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

import (
	"bytes"
	"fmt"
	"io"
7
	"sort"
8 9 10 11 12 13 14
	"text/tabwriter"

	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"
15
	merkledag "github.com/ipfs/go-ipfs/merkledag"
16 17 18 19 20
)

type LsLink struct {
	Name, Hash string
	Size       uint64
21
	Type       string
22 23 24
}

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

type LsOutput struct {
32 33
	Arguments map[string]string
	Objects   map[string]*LsObject
34 35 36 37
}

var LsCmd = &cmds.Command{
	Helptext: cmds.HelpText{
38
		Tagline: "List directory contents for Unix filesystem objects.",
39
		ShortDescription: `
Richard Littauer's avatar
Richard Littauer committed
40
Displays the contents  of an IPFS or IPNS object(s) at the given path.
41

42 43
The JSON output contains size information. For files, the child size
is the total size of the file contents. For directories, the child
44
size is the IPFS link size.
45 46 47 48
`,
	},

	Arguments: []cmds.Argument{
49
		cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to list links from.").EnableStdin(),
50 51
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
52
		node, err := req.InvocContext().GetNode()
53 54 55 56 57 58 59
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		paths := req.Arguments()

60 61 62 63 64 65
		output := LsOutput{
			Arguments: map[string]string{},
			Objects:   map[string]*LsObject{},
		}

		for _, fpath := range paths {
Jeromy's avatar
Jeromy committed
66
			ctx := req.Context()
67
			merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
68 69 70 71 72
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

73
			key, err := merkleNode.Key()
74 75 76
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
77
			}
78

79 80 81 82 83 84 85 86 87 88 89 90 91
			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
			}
92 93

			t := unixFSNode.GetType()
94 95 96 97 98 99 100

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

101 102
			switch t {
			case unixfspb.Data_File:
103
				break
104
			case unixfspb.Data_Directory:
105 106 107
				links := make([]LsLink, len(merkleNode.Links))
				output.Objects[hash].Links = links
				for i, link := range merkleNode.Links {
108 109
					var linkNode *merkledag.Node
					linkNode, err = link.GetNode(ctx, node.DAG)
110 111 112 113
					if err != nil {
						res.SetError(err, cmds.ErrNormal)
						return
					}
114
					d, err := unixfs.FromBytes(linkNode.Data)
115 116 117 118
					if err != nil {
						res.SetError(err, cmds.ErrNormal)
						return
					}
119
					t := d.GetType()
120 121 122
					lsLink := LsLink{
						Name: link.Name,
						Hash: link.Hash.B58String(),
123
						Type: t.String(),
124
					}
125
					if t == unixfspb.Data_File {
126 127 128 129
						lsLink.Size = d.GetFilesize()
					} else {
						lsLink.Size = link.Size
					}
130
					links[i] = lsLink
131
				}
Jeromy's avatar
Jeromy committed
132 133 134 135 136 137
			case unixfspb.Data_Symlink:
				res.SetError(fmt.Errorf("cannot list symlinks yet"), cmds.ErrNormal)
				return
			default:
				res.SetError(fmt.Errorf("unrecognized type: %s", t), cmds.ErrImplementation)
				return
138 139 140
			}
		}

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

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

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

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