ls.go 4.46 KB
Newer Older
1 2 3 4 5 6
package unixfs

import (
	"bytes"
	"fmt"
	"io"
7
	"sort"
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
	"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
23
	Type       string
24 25 26
}

type LsObject struct {
27 28 29
	Hash  string
	Size  uint64
	Type  string
30
	Links []LsLink
31 32 33
}

type LsOutput struct {
34 35
	Arguments map[string]string
	Objects   map[string]*LsObject
36 37 38 39 40 41 42
}

var LsCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List directory contents for Unix-filesystem objects",
		ShortDescription: `
Retrieves the object named by <ipfs-or-ipns-path> and displays the
43
contents.
44

45 46 47
The JSON output contains size information.  For files, the child size
is the total size of the file contents.  For directories, the child
size is the IPFS link size.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
`,
	},

	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()

63 64 65 66 67 68
		output := LsOutput{
			Arguments: map[string]string{},
			Objects:   map[string]*LsObject{},
		}

		for _, fpath := range paths {
69 70
			ctx := req.Context().Context
			merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
71 72 73 74 75
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

76
			key, err := merkleNode.Key()
77 78 79
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
80
			}
81

82 83 84 85 86 87 88 89 90 91 92 93 94
			hash := key.B58String()
			output.Arguments[fpath] = hash

			if _, ok := output.Objects[hash]; ok {
				// duplicate argument for an already-listed node
				continue
			}

			unixFSNode, err := unixfs.FromBytes(merkleNode.Data)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
95 96

			t := unixFSNode.GetType()
97 98 99 100 101 102 103

			output.Objects[hash] = &LsObject{
				Hash: key.String(),
				Type: t.String(),
				Size: unixFSNode.GetFilesize(),
			}

104 105 106 107 108
			switch t {
			default:
				res.SetError(fmt.Errorf("unrecognized type: %s", t), cmds.ErrImplementation)
				return
			case unixfspb.Data_File:
109
				break
110
			case unixfspb.Data_Directory:
111 112 113
				links := make([]LsLink, len(merkleNode.Links))
				output.Objects[hash].Links = links
				for i, link := range merkleNode.Links {
114
					getCtx, cancel := context.WithTimeout(ctx, time.Minute)
115 116 117 118 119 120 121 122 123 124 125
					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
					}
126
					t := d.GetType()
127 128 129
					lsLink := LsLink{
						Name: link.Name,
						Hash: link.Hash.B58String(),
130
						Type: t.String(),
131
					}
132
					if t == unixfspb.Data_File {
133 134 135 136
						lsLink.Size = d.GetFilesize()
					} else {
						lsLink.Size = link.Size
					}
137
					links[i] = lsLink
138 139 140 141
				}
			}
		}

142
		res.SetOutput(&output)
143 144 145 146 147 148 149
	},
	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)
150 151 152 153 154 155 156 157 158

			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)
				}

159
				if object.Type == "Directory" {
160
					directories = append(directories, argument)
161 162
				} else {
					nonDirectories = append(nonDirectories, argument)
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
				}
			}
			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)
				}
184 185 186 187 188
				if len(output.Arguments) > 1 {
					for _, arg := range directories[i:] {
						if output.Arguments[arg] == hash {
							fmt.Fprintf(w, "%s:\n", arg)
						}
189
					}
190 191 192 193 194 195 196 197 198 199 200 201
				}
				for _, link := range object.Links {
					fmt.Fprintf(w, "%s\n", link.Name)
				}
			}
			w.Flush()

			return buf, nil
		},
	},
	Type: LsOutput{},
}