From 434871ba18662db366907e358654728a357f9c0e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 9 Jun 2015 10:03:52 -0700 Subject: [PATCH 1/9] core/commands/unixfs: Add 'ipfs unixfs ls ...' This is similar to 'ipfs ls ...', but it: * Lists file sizes that match the content size: $ ipfs --encoding=json unixfs ls /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4 { "Objects": [ { "Argument": "/ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4", "Links": [ { "Name": "busybox", "Hash": "QmPbjmmci73roXf9VijpyQGgRJZthiQfnEetaMRGoGYV5a", "Size": 1947624, "Type": 2 } ] } ] } $ ipfs cat /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4/busybox | wc -c 1947624 'ipfs ls ...', on the other hand, is using the Merkle-descendant size, which also includes fanout links and the typing information unixfs objects store in their Data: $ ipfs --encoding=json ls /ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4 { "Objects": [ { "Hash": "/ipfs/QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4", "Links": [ { "Name": "busybox", "Hash": "QmPbjmmci73roXf9VijpyQGgRJZthiQfnEetaMRGoGYV5a", "Size": 1948128, "Type": 2 } ] } ] } * Has a simpler text output corresponding to POSIX ls [1]: $ ipfs unixfs ls /ipfs/QmV2FrBtvue5ve7vxbAzKz3mTdWq8wfMNPwYd8d9KHksCF/gentoo/stage3/amd64/2015-04-02 bin dev etc proc run sys $ ipfs ls /ipfs/QmV2FrBtvue5ve7vxbAzKz3mTdWq8wfMNPwYd8d9KHksCF/gentoo/stage3/amd64/2015-04-02 QmSRCHG21Sbqm3EJG9aEBo4vS7Fqu86pAjqf99MyCdNxZ4 1948183 bin/ QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn 4 dev/ QmUz1Z5jnQEjwr78fiMk5babwjJBDmhN5sx5HvPiTGGGjM 1207 etc/ QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn 4 proc/ QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn 4 run/ QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn 4 sys/ The minimal output allows us to start off with POSIX compliance and then add options (which may or may not be POSIX compatible) to adjust the output format as we get a better feel for what we need ([2] through [3]). [1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html [2]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41724727&page=5 [3]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41725146&page=5 License: MIT Signed-off-by: W. Trevor King --- core/commands/root.go | 3 + core/commands/unixfs/ls.go | 124 +++++++++++++++++++++++++++++++ core/commands/unixfs/unixfs.go | 21 ++++++ test/sharness/t0200-unixfs-ls.sh | 75 +++++++++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 core/commands/unixfs/ls.go create mode 100644 core/commands/unixfs/unixfs.go create mode 100755 test/sharness/t0200-unixfs-ls.sh diff --git a/core/commands/root.go b/core/commands/root.go index 916b32b63f8..0c91208eb16 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -5,6 +5,7 @@ import ( "strings" cmds "github.com/ipfs/go-ipfs/commands" + unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs" evlog "github.com/ipfs/go-ipfs/thirdparty/eventlog" ) @@ -35,6 +36,7 @@ DATA STRUCTURE COMMANDS block Interact with raw blocks in the datastore object Interact with raw dag nodes + unixfs Interact with Unix filesystem objects ADVANCED COMMANDS @@ -102,6 +104,7 @@ var rootSubcommands = map[string]*cmds.Command{ "stats": StatsCmd, "swarm": SwarmCmd, "tour": tourCmd, + "unixfs": unixfs.UnixFSCmd, "update": UpdateCmd, "version": VersionCmd, "bitswap": BitswapCmd, diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go new file mode 100644 index 00000000000..58ad2f811ef --- /dev/null +++ b/core/commands/unixfs/ls.go @@ -0,0 +1,124 @@ +package unixfs + +import ( + "bytes" + "fmt" + "io" + "text/tabwriter" + "time" + + context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" + + cmds "github.com/ipfs/go-ipfs/commands" + core "github.com/ipfs/go-ipfs/core" + path "github.com/ipfs/go-ipfs/path" + unixfs "github.com/ipfs/go-ipfs/unixfs" + unixfspb "github.com/ipfs/go-ipfs/unixfs/pb" +) + +type LsLink struct { + Name, Hash string + Size uint64 + Type unixfspb.Data_DataType +} + +type LsObject struct { + Argument string + Links []LsLink +} + +type LsOutput struct { + Objects []*LsObject +} + +var LsCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "List directory contents for Unix-filesystem objects", + ShortDescription: ` +Retrieves the object named by and displays the +contents with the following format: + + + +For files, the child size is the total size of the file contents. For +directories, the child size is the IPFS link size. +`, + }, + + Arguments: []cmds.Argument{ + cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to list links from").EnableStdin(), + }, + Run: func(req cmds.Request, res cmds.Response) { + node, err := req.Context().GetNode() + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + paths := req.Arguments() + + output := make([]*LsObject, len(paths)) + for i, fpath := range paths { + dagnode, err := core.Resolve(req.Context().Context, node, path.Path(fpath)) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } + + output[i] = &LsObject{ + Argument: fpath, + Links: make([]LsLink, len(dagnode.Links)), + } + 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) + 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 + } + } + + res.SetOutput(&LsOutput{Objects: output}) + }, + Marshalers: cmds.MarshalerMap{ + cmds.Text: func(res cmds.Response) (io.Reader, error) { + + 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 { + fmt.Fprintf(w, "%s:\n", object.Argument) + } + for _, link := range object.Links { + fmt.Fprintf(w, "%s\n", link.Name) + } + if len(output.Objects) > 1 { + fmt.Fprintln(w) + } + } + w.Flush() + + return buf, nil + }, + }, + Type: LsOutput{}, +} diff --git a/core/commands/unixfs/unixfs.go b/core/commands/unixfs/unixfs.go new file mode 100644 index 00000000000..de97c8a3320 --- /dev/null +++ b/core/commands/unixfs/unixfs.go @@ -0,0 +1,21 @@ +package unixfs + +import cmds "github.com/ipfs/go-ipfs/commands" + +var UnixFSCmd = &cmds.Command{ + Helptext: cmds.HelpText{ + Tagline: "Interact with ipfs objects representing Unix filesystems", + ShortDescription: ` +'ipfs unixfs' provides a familar interface to filesystems represtented +by IPFS objects that hides IPFS-implementation details like layout +objects (e.g. fanout and chunking). +`, + Synopsis: ` +ipfs unixfs ls ... - List directory contents for ... +`, + }, + + Subcommands: map[string]*cmds.Command{ + "ls": LsCmd, + }, +} diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh new file mode 100755 index 00000000000..f5dc20d3054 --- /dev/null +++ b/test/sharness/t0200-unixfs-ls.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# +# Copyright (c) 2014 Christian Couder +# MIT Licensed; see the LICENSE file in this repository. +# + +test_description="Test unixfs ls command" + +. lib/test-lib.sh + +test_init_ipfs + +test_ls_cmd() { + + test_expect_success "'ipfs add -r testData' succeeds" ' + mkdir -p testData testData/d1 testData/d2 && + echo "test" >testData/f1 && + echo "data" >testData/f2 && + echo "hello" >testData/d1/a && + random 128 42 >testData/d1/128 && + echo "world" >testData/d2/a && + random 1024 42 >testData/d2/1024 && + ipfs add -r testData >actual_add + ' + + test_expect_success "'ipfs add' output looks good" ' + cat <<-\EOF >expected_add && + added QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe testData/d1/128 + added QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN testData/d1/a + added QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss testData/d1 + added QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd testData/d2/1024 + added QmaRGe7bVmVaLmxbrMiVNXqW4pRNNp3xq7hFtyRKA3mtJL testData/d2/a + added QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy testData/d2 + added QmeomffUNfmQy76CQGy9NdmqEnnHU9soCexBnGU3ezPHVH testData/f1 + added QmNtocSs7MoDkJMc1RkyisCSKvLadujPsfJfSdJ3e1eA1M testData/f2 + added QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj testData + EOF + test_cmp expected_add actual_add + ' + + test_expect_success "'ipfs unixfs ls ' succeeds" ' + ipfs unixfs ls QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss >actual_ls + ' + + test_expect_success "'ipfs unixfs ls ' output looks good" ' + cat <<-\EOF >expected_ls && + QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj: + d1 + d2 + f1 + f2 + + QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy: + 1024 + a + + QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss: + 128 + a + + EOF + test_cmp expected_ls actual_ls + ' +} + + +# should work offline +test_ls_cmd + +# should work online +test_launch_ipfs_daemon +test_ls_cmd +test_kill_ipfs_daemon + +test_done From 663f37cb999f55cc18cc27b71c4df83afdc8fa10 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 9 Jun 2015 14:06:33 -0700 Subject: [PATCH 2/9] 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 --- 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 a720a821454..657dd0403e2 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 58ad2f811ef..9d9cdbdbb55 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 f5dc20d3054..4d980e44e46 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 ' succeeds" ' + ipfs unixfs ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe >actual_ls_file + ' + + test_expect_success "'ipfs unixfs ls ' output looks good" ' + cat <<-\EOF >expected_ls_file && + /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 + QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe + EOF + test_cmp expected_ls_file actual_ls_file + ' } From 3e6905e8bae04485f05525e853cb1c9945b69e8f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 9 Jun 2015 16:21:38 -0700 Subject: [PATCH 3/9] core/commands/unixfs: Rename 'ipfs unixfs' to 'ipfs file' To be less confusing to newcomers (the IPFS filesystem isn't Unix-specific anyway, and it isn't even very POSIX-specific [1,2,3]). I'm a bit uncertain about having one name for users and another for devs, but the consensus seems to be that mainaining two names is worth the trouble [4]. We also kicked around: * 'files' (plural), * 'filesystem' (too long), and * 'fs' (redundant after 'ipfs', even though IPFS isn't just about filesystems) on IRC [5 through 6]. I wish there was a more evocative term. I'm never sure where "file" lands on the scale between "filesysytem", "everything is a file", "a single chunk of data with an associated inode". But we can't think of anything better. [1]: https://github.com/ipfs/go-ipfs/pull/1348#issuecomment-110529070 [2]: https://github.com/ipfs/go-ipfs/pull/1348#issuecomment-110529921 [3]: https://github.com/ipfs/go-ipfs/pull/1136/files#r29377283 In my response to this (no longer visibile on GitHub): On Wed, Apr 29, 2015 at 01:30:04PM -0700, Juan Batiz-Benet wrote: > > +package fsnode > > i think this package should be called `unixfs` as that's the > abstraction that this is calling to. Will do, although I don't see what's especially Unix-y about these file nodes. [4]: https://github.com/ipfs/go-ipfs/pull/1348#issuecomment-110529811 [5]: https://botbot.me/freenode/ipfs/2015-06-09/?msg=41428456&page=5 [6]: https://botbot.me/freenode/ipfs/2015-06-09/?msg=41430703&page=5 License: MIT Signed-off-by: W. Trevor King --- core/commands/root.go | 4 ++-- core/commands/unixfs/unixfs.go | 4 ++-- test/sharness/t0200-unixfs-ls.sh | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/commands/root.go b/core/commands/root.go index 0c91208eb16..13d45916266 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -36,7 +36,7 @@ DATA STRUCTURE COMMANDS block Interact with raw blocks in the datastore object Interact with raw dag nodes - unixfs Interact with Unix filesystem objects + file Interact with Unix filesystem objects ADVANCED COMMANDS @@ -104,7 +104,7 @@ var rootSubcommands = map[string]*cmds.Command{ "stats": StatsCmd, "swarm": SwarmCmd, "tour": tourCmd, - "unixfs": unixfs.UnixFSCmd, + "file": unixfs.UnixFSCmd, "update": UpdateCmd, "version": VersionCmd, "bitswap": BitswapCmd, diff --git a/core/commands/unixfs/unixfs.go b/core/commands/unixfs/unixfs.go index de97c8a3320..67a30c6cbd5 100644 --- a/core/commands/unixfs/unixfs.go +++ b/core/commands/unixfs/unixfs.go @@ -6,12 +6,12 @@ var UnixFSCmd = &cmds.Command{ Helptext: cmds.HelpText{ Tagline: "Interact with ipfs objects representing Unix filesystems", ShortDescription: ` -'ipfs unixfs' provides a familar interface to filesystems represtented +'ipfs file' provides a familar interface to filesystems represtented by IPFS objects that hides IPFS-implementation details like layout objects (e.g. fanout and chunking). `, Synopsis: ` -ipfs unixfs ls ... - List directory contents for ... +ipfs file ls ... - List directory contents for ... `, }, diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh index 4d980e44e46..ad6bf280a3f 100755 --- a/test/sharness/t0200-unixfs-ls.sh +++ b/test/sharness/t0200-unixfs-ls.sh @@ -4,7 +4,7 @@ # MIT Licensed; see the LICENSE file in this repository. # -test_description="Test unixfs ls command" +test_description="Test file ls command" . lib/test-lib.sh @@ -38,11 +38,11 @@ test_ls_cmd() { test_cmp expected_add actual_add ' - test_expect_success "'ipfs unixfs ls ' succeeds" ' - ipfs unixfs ls QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss >actual_ls + test_expect_success "'ipfs file ls ' succeeds" ' + ipfs file ls QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss >actual_ls ' - test_expect_success "'ipfs unixfs ls ' output looks good" ' + test_expect_success "'ipfs file ls ' output looks good" ' cat <<-\EOF >expected_ls && QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj: d1 @@ -61,11 +61,11 @@ test_ls_cmd() { test_cmp expected_ls actual_ls ' - test_expect_success "'ipfs unixfs ls ' succeeds" ' - ipfs unixfs ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe >actual_ls_file + test_expect_success "'ipfs file ls ' succeeds" ' + ipfs file ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe >actual_ls_file ' - test_expect_success "'ipfs unixfs ls ' output looks good" ' + test_expect_success "'ipfs file ls ' output looks good" ' cat <<-\EOF >expected_ls_file && /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe From c9733c5da78dc0c04f6c5b95c5504ee3ae936bc9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 9 Jun 2015 15:43:03 -0700 Subject: [PATCH 4/9] core/commands/unixfs/ls: Set Argument in JSON output Change the approach to the directory-header control so we can set the Argument value in the JSON response. Stripping the trailing newline from the JSON output is annoying, but looking over [1] I saw no easy way to add a newline to the JSON output. And with the general framework that commands/ attempts to be, it feels a bit funny to customize the JSON output for a command-line program. Perhaps a workable solution is to have the command-line client append newlines to any output that otherwise lacks them? But that seems like a change best left to a separate series. [1]: http://golang.org/pkg/encoding/json/ License: MIT Signed-off-by: W. Trevor King --- core/commands/unixfs/ls.go | 7 ++++--- test/sharness/t0200-unixfs-ls.sh | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go index 9d9cdbdbb55..b1dc2102167 100644 --- a/core/commands/unixfs/ls.go +++ b/core/commands/unixfs/ls.go @@ -72,7 +72,7 @@ directories, the child size is the IPFS link size. return } - output[i] = &LsObject{} + output[i] = &LsObject{Argument: fpath} t := unixFSNode.GetType() switch t { @@ -92,7 +92,6 @@ directories, the child size is the IPFS link size. 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) @@ -132,7 +131,9 @@ directories, the child size is the IPFS link size. w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0) lastObjectDirHeader := false for i, object := range output.Objects { - if len(output.Objects) > 1 && object.Argument != "" { + singleObject := (len(object.Links) == 1 && + object.Links[0].Name == object.Argument) + if len(output.Objects) > 1 && !singleObject { if i > 0 { fmt.Fprintln(w) } diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh index ad6bf280a3f..3ba7c82ff47 100755 --- a/test/sharness/t0200-unixfs-ls.sh +++ b/test/sharness/t0200-unixfs-ls.sh @@ -72,6 +72,32 @@ test_ls_cmd() { EOF test_cmp expected_ls_file actual_ls_file ' + + test_expect_success "'ipfs --encoding=json file ls ' succeeds" ' + ipfs --encoding=json file ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 >actual_json_ls_file + ' + + test_expect_success "'ipfs --encoding=json file ls ' output looks good" ' + cat <<-\EOF >expected_json_ls_file_trailing_newline && + { + "Objects": [ + { + "Argument": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", + "Links": [ + { + "Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", + "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", + "Size": 1024, + "Type": 2 + } + ] + } + ] + } + EOF + printf %s "$(cat expected_json_ls_file_trailing_newline)" >expected_json_ls_file && + test_cmp expected_json_ls_file actual_json_ls_file + ' } From f0a0ac1b8351812dcb6ee97d3c72a51cd95578d4 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 10 Jun 2015 06:58:02 -0700 Subject: [PATCH 5/9] core/commands/unixfs/ls: Use a stringified type name This doesn't affect the text output, which was already using a stringified name. The earlier stringification does change the JSON output from an enumeration integer (e.g. 2) to the string form (e.g. "File"). If/when we transition to Merkle-object types named by their hash, we will probably want to revisit this and pass both the type hash and human-readable-but-collision-prone name on to clients. License: MIT Signed-off-by: W. Trevor King --- core/commands/unixfs/ls.go | 9 +++++---- test/sharness/t0200-unixfs-ls.sh | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go index b1dc2102167..3ed9d373343 100644 --- a/core/commands/unixfs/ls.go +++ b/core/commands/unixfs/ls.go @@ -19,7 +19,7 @@ import ( type LsLink struct { Name, Hash string Size uint64 - Type unixfspb.Data_DataType + Type string } type LsObject struct { @@ -88,7 +88,7 @@ directories, the child size is the IPFS link size. output[i].Links = []LsLink{LsLink{ Name: fpath, Hash: key.String(), - Type: t, + Type: t.String(), Size: unixFSNode.GetFilesize(), }} case unixfspb.Data_Directory: @@ -106,12 +106,13 @@ directories, the child size is the IPFS link size. res.SetError(err, cmds.ErrNormal) return } + t := d.GetType() lsLink := LsLink{ Name: link.Name, Hash: link.Hash.B58String(), - Type: d.GetType(), + Type: t.String(), } - if lsLink.Type == unixfspb.Data_File { + if t == unixfspb.Data_File { lsLink.Size = d.GetFilesize() } else { lsLink.Size = link.Size diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh index 3ba7c82ff47..38588c9a2ca 100755 --- a/test/sharness/t0200-unixfs-ls.sh +++ b/test/sharness/t0200-unixfs-ls.sh @@ -88,7 +88,7 @@ test_ls_cmd() { "Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", "Size": 1024, - "Type": 2 + "Type": "File" } ] } From ce0bf803684aa48ee719a7279f1448060ae5f121 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 13 Jun 2015 12:50:56 -0700 Subject: [PATCH 6/9] core/commands/unixfs/ls: Replace TODO context with command context Discussing this on IRC ([1] through [2]), Jeromy and I decided that we'd really like a way to configure per-command [3] and per-action timeouts, but until we have that we want to leave the minute limit here. We also decided that the use of TODO here instead of the per-command req.Context().Context was a bug, which I'm fixing with this commit. [1]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41714126&page=4 [2]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41715618&page=4 [3]: https://github.com/ipfs/go-ipfs/issues/1325 License: MIT Signed-off-by: W. Trevor King --- core/commands/unixfs/ls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go index 3ed9d373343..9708800f915 100644 --- a/core/commands/unixfs/ls.go +++ b/core/commands/unixfs/ls.go @@ -94,7 +94,7 @@ directories, the child size is the IPFS link size. case unixfspb.Data_Directory: output[i].Links = make([]LsLink, len(merkleNode.Links)) for j, link := range merkleNode.Links { - getCtx, cancel := context.WithTimeout(context.TODO(), time.Minute) + getCtx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() link.Node, err = link.GetNode(getCtx, node.DAG) if err != nil { From 7fc2410d9504a029ccbb7f28b87a0525ea36aadc Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 13 Jun 2015 13:35:01 -0700 Subject: [PATCH 7/9] core/commands/unixfs/ls: Hash-map for Objects Discussion with Juan on IRC ([1] through [2]) lead to this adjusted JSON output. Benefits over the old output include: * deduplication (we only check the children of a given Merkle node once, even if multiple arguments resolve to that hash) * alphabetized output (like POSIX's ls). As a side-effect of this change, I'm also matching GNU Coreutils' ls output (maybe in POSIX?) by printing an alphabetized list of non-directories (one per line) first, with alphabetized directory lists afterwards. [1]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41725570&page=5 [2]: https://botbot.me/freenode/ipfs/2015-06-12/?msg=41726547&page=5 License: MIT Signed-off-by: W. Trevor King --- core/commands/unixfs/ls.go | 93 +++++++++++++++++++++++--------- test/sharness/t0200-unixfs-ls.sh | 86 +++++++++++++++++++++++++---- 2 files changed, 144 insertions(+), 35 deletions(-) diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go index 9708800f915..ed1f70a88ea 100644 --- a/core/commands/unixfs/ls.go +++ b/core/commands/unixfs/ls.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "sort" "text/tabwriter" "time" @@ -23,12 +24,12 @@ type LsLink struct { } type LsObject struct { - Argument string - Links []LsLink + Links []LsLink } type LsOutput struct { - Objects []*LsObject + Arguments map[string]string + Objects map[string]*LsObject } var LsCmd = &cmds.Command{ @@ -57,8 +58,12 @@ directories, the child size is the IPFS link size. paths := req.Arguments() - output := make([]*LsObject, len(paths)) - for i, fpath := range paths { + output := LsOutput{ + Arguments: map[string]string{}, + Objects: map[string]*LsObject{}, + } + + for _, fpath := range paths { ctx := req.Context().Context merkleNode, err := core.Resolve(ctx, node, path.Path(fpath)) if err != nil { @@ -66,13 +71,27 @@ directories, the child size is the IPFS link size. return } - unixFSNode, err := unixfs.FromBytes(merkleNode.Data) + key, err := merkleNode.Key() if err != nil { res.SetError(err, cmds.ErrNormal) return } - output[i] = &LsObject{Argument: fpath} + hash := key.B58String() + output.Arguments[fpath] = hash + + if _, ok := output.Objects[hash]; ok { + // duplicate argument for an already-listed node + continue + } + + output.Objects[hash] = &LsObject{} + + unixFSNode, err := unixfs.FromBytes(merkleNode.Data) + if err != nil { + res.SetError(err, cmds.ErrNormal) + return + } t := unixFSNode.GetType() switch t { @@ -85,15 +104,16 @@ directories, the child size is the IPFS link size. res.SetError(err, cmds.ErrNormal) return } - output[i].Links = []LsLink{LsLink{ + output.Objects[hash].Links = []LsLink{LsLink{ Name: fpath, Hash: key.String(), Type: t.String(), Size: unixFSNode.GetFilesize(), }} case unixfspb.Data_Directory: - output[i].Links = make([]LsLink, len(merkleNode.Links)) - for j, link := range merkleNode.Links { + links := make([]LsLink, len(merkleNode.Links)) + output.Objects[hash].Links = links + for i, link := range merkleNode.Links { getCtx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() link.Node, err = link.GetNode(getCtx, node.DAG) @@ -117,12 +137,12 @@ directories, the child size is the IPFS link size. } else { lsLink.Size = link.Size } - output[i].Links[j] = lsLink + links[i] = lsLink } } } - res.SetOutput(&LsOutput{Objects: output}) + res.SetOutput(&output) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -130,21 +150,44 @@ 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) - lastObjectDirHeader := false - for i, object := range output.Objects { - singleObject := (len(object.Links) == 1 && - object.Links[0].Name == object.Argument) - if len(output.Objects) > 1 && !singleObject { - if i > 0 { - fmt.Fprintln(w) - } - fmt.Fprintf(w, "%s:\n", object.Argument) - lastObjectDirHeader = true + + nonDirectories := []string{} + directories := []string{} + for argument, hash := range output.Arguments { + object, ok := output.Objects[hash] + if !ok { + return nil, fmt.Errorf("unresolved hash: %s", hash) + } + + if len(object.Links) == 1 && object.Links[0].Hash == hash { + nonDirectories = append(nonDirectories, argument) } else { - if lastObjectDirHeader { - fmt.Fprintln(w) + directories = append(directories, argument) + } + } + sort.Strings(nonDirectories) + sort.Strings(directories) + + for _, argument := range nonDirectories { + fmt.Fprintf(w, "%s\n", argument) + } + + seen := map[string]bool{} + for i, argument := range directories { + hash := output.Arguments[argument] + if _, ok := seen[hash]; ok { + continue + } + seen[hash] = true + + object := output.Objects[hash] + if i > 0 || len(nonDirectories) > 0 { + fmt.Fprintln(w) + } + for _, arg := range directories[i:] { + if output.Arguments[arg] == hash { + fmt.Fprintf(w, "%s:\n", arg) } - lastObjectDirHeader = false } for _, link := range object.Links { fmt.Fprintf(w, "%s\n", link.Name) diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh index 38588c9a2ca..51ba0614bbe 100755 --- a/test/sharness/t0200-unixfs-ls.sh +++ b/test/sharness/t0200-unixfs-ls.sh @@ -44,12 +44,6 @@ test_ls_cmd() { test_expect_success "'ipfs file ls ' output looks good" ' cat <<-\EOF >expected_ls && - QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj: - d1 - d2 - f1 - f2 - QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy: 1024 a @@ -57,6 +51,12 @@ test_ls_cmd() { QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss: 128 a + + QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj: + d1 + d2 + f1 + f2 EOF test_cmp expected_ls actual_ls ' @@ -73,6 +73,23 @@ test_ls_cmd() { test_cmp expected_ls_file actual_ls_file ' + test_expect_success "'ipfs file ls ' succeeds" ' + ipfs file ls /ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1 /ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 /ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd >actual_ls_duplicates_file + ' + + test_expect_success "'ipfs file ls ' output looks good" ' + cat <<-\EOF >expected_ls_duplicates_file && + /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 + /ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd + + /ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss: + /ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1: + 128 + a + EOF + test_cmp expected_ls_duplicates_file actual_ls_duplicates_file + ' + test_expect_success "'ipfs --encoding=json file ls ' succeeds" ' ipfs --encoding=json file ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 >actual_json_ls_file ' @@ -80,9 +97,11 @@ test_ls_cmd() { test_expect_success "'ipfs --encoding=json file ls ' output looks good" ' cat <<-\EOF >expected_json_ls_file_trailing_newline && { - "Objects": [ - { - "Argument": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", + "Arguments": { + "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd" + }, + "Objects": { + "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": { "Links": [ { "Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", @@ -92,12 +111,59 @@ test_ls_cmd() { } ] } - ] + } } EOF printf %s "$(cat expected_json_ls_file_trailing_newline)" >expected_json_ls_file && test_cmp expected_json_ls_file actual_json_ls_file ' + + test_expect_success "'ipfs --encoding=json file ls ' succeeds" ' + ipfs --encoding=json file ls /ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1 /ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 /ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd >actual_json_ls_duplicates_file + ' + + test_expect_success "'ipfs --encoding=json file ls ' output looks good" ' + cat <<-\EOF >expected_json_ls_duplicates_file_trailing_newline && + { + "Arguments": { + "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", + "/ipfs/QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss": "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss", + "/ipfs/QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", + "/ipfs/QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj/d1": "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss" + }, + "Objects": { + "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss": { + "Links": [ + { + "Name": "128", + "Hash": "QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe", + "Size": 128, + "Type": "File" + }, + { + "Name": "a", + "Hash": "QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN", + "Size": 6, + "Type": "File" + } + ] + }, + "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": { + "Links": [ + { + "Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", + "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", + "Size": 1024, + "Type": "File" + } + ] + } + } + } + EOF + printf %s "$(cat expected_json_ls_duplicates_file_trailing_newline)" >expected_json_ls_duplicates_file && + test_cmp expected_json_ls_duplicates_file actual_json_ls_duplicates_file + ' } From 5fd4812b2087bc634b33e3798b4d128854df5914 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 18 Jun 2015 16:31:18 -0700 Subject: [PATCH 8/9] core/commands/unixfs/ls.go: Fix (and test) single-directory listing We don't want to prefix these results with the argument. If there was only one argument, the unprefixed results are still explicit. License: MIT Signed-off-by: W. Trevor King --- core/commands/unixfs/ls.go | 8 +++++--- test/sharness/t0200-unixfs-ls.sh | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go index ed1f70a88ea..ed2c0b1339d 100644 --- a/core/commands/unixfs/ls.go +++ b/core/commands/unixfs/ls.go @@ -184,9 +184,11 @@ directories, the child size is the IPFS link size. if i > 0 || len(nonDirectories) > 0 { fmt.Fprintln(w) } - for _, arg := range directories[i:] { - if output.Arguments[arg] == hash { - fmt.Fprintf(w, "%s:\n", arg) + if len(output.Arguments) > 1 { + for _, arg := range directories[i:] { + if output.Arguments[arg] == hash { + fmt.Fprintf(w, "%s:\n", arg) + } } } for _, link := range object.Links { diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh index 51ba0614bbe..a79c098a495 100755 --- a/test/sharness/t0200-unixfs-ls.sh +++ b/test/sharness/t0200-unixfs-ls.sh @@ -38,12 +38,24 @@ test_ls_cmd() { test_cmp expected_add actual_add ' + test_expect_success "'ipfs file ls ' succeeds" ' + ipfs file ls QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy >actual_ls_one_directory + ' + + test_expect_success "'ipfs file ls ' output looks good" ' + cat <<-\EOF >expected_ls_one_directory && + 1024 + a + EOF + test_cmp expected_ls_one_directory actual_ls_one_directory + ' + test_expect_success "'ipfs file ls ' succeeds" ' - ipfs file ls QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss >actual_ls + ipfs file ls QmfNy183bXiRVyrhyWtq3TwHn79yHEkiAGFr18P7YNzESj QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss >actual_ls_three_directories ' test_expect_success "'ipfs file ls ' output looks good" ' - cat <<-\EOF >expected_ls && + cat <<-\EOF >expected_ls_three_directories && QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy: 1024 a @@ -58,7 +70,7 @@ test_ls_cmd() { f1 f2 EOF - test_cmp expected_ls actual_ls + test_cmp expected_ls_three_directories actual_ls_three_directories ' test_expect_success "'ipfs file ls ' succeeds" ' From 4acab79d66f7d8f0c82385ec2456b3d0dcea42de Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 18 Jun 2015 17:10:47 -0700 Subject: [PATCH 9/9] core/commands/unixfs/ls: Explicitily record stat in LsObject Instead of abusing a LsLink for non-directory objects [1]. [1]: https://github.com/ipfs/go-ipfs/pull/1348#discussion_r32680669 License: MIT Signed-off-by: W. Trevor King --- core/commands/unixfs/ls.go | 30 ++++++++++++++---------------- test/sharness/t0200-unixfs-ls.sh | 27 +++++++++++---------------- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go index ed2c0b1339d..989f6576022 100644 --- a/core/commands/unixfs/ls.go +++ b/core/commands/unixfs/ls.go @@ -24,6 +24,9 @@ type LsLink struct { } type LsObject struct { + Hash string + Size uint64 + Type string Links []LsLink } @@ -85,8 +88,6 @@ directories, the child size is the IPFS link size. continue } - output.Objects[hash] = &LsObject{} - unixFSNode, err := unixfs.FromBytes(merkleNode.Data) if err != nil { res.SetError(err, cmds.ErrNormal) @@ -94,22 +95,19 @@ directories, the child size is the IPFS link size. } t := unixFSNode.GetType() + + output.Objects[hash] = &LsObject{ + Hash: key.String(), + Type: t.String(), + Size: unixFSNode.GetFilesize(), + } + 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 - } - output.Objects[hash].Links = []LsLink{LsLink{ - Name: fpath, - Hash: key.String(), - Type: t.String(), - Size: unixFSNode.GetFilesize(), - }} + break case unixfspb.Data_Directory: links := make([]LsLink, len(merkleNode.Links)) output.Objects[hash].Links = links @@ -159,10 +157,10 @@ directories, the child size is the IPFS link size. return nil, fmt.Errorf("unresolved hash: %s", hash) } - if len(object.Links) == 1 && object.Links[0].Hash == hash { - nonDirectories = append(nonDirectories, argument) - } else { + if object.Type == "Directory" { directories = append(directories, argument) + } else { + nonDirectories = append(nonDirectories, argument) } } sort.Strings(nonDirectories) diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh index a79c098a495..653b3872f9c 100755 --- a/test/sharness/t0200-unixfs-ls.sh +++ b/test/sharness/t0200-unixfs-ls.sh @@ -114,14 +114,10 @@ test_ls_cmd() { }, "Objects": { "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": { - "Links": [ - { - "Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", - "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", - "Size": 1024, - "Type": "File" - } - ] + "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", + "Size": 1024, + "Type": "File", + "Links": null } } } @@ -145,6 +141,9 @@ test_ls_cmd() { }, "Objects": { "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss": { + "Hash": "QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss", + "Size": 0, + "Type": "Directory", "Links": [ { "Name": "128", @@ -161,14 +160,10 @@ test_ls_cmd() { ] }, "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd": { - "Links": [ - { - "Name": "/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024", - "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", - "Size": 1024, - "Type": "File" - } - ] + "Hash": "QmbQBUSRL9raZtNXfpTDeaxQapibJEG6qEY8WqAN22aUzd", + "Size": 1024, + "Type": "File", + "Links": null } } }