ls.go 4.4 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
	"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"
)

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

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

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

var LsCmd = &cmds.Command{
	Helptext: cmds.HelpText{
rht's avatar
rht committed
37
		Tagline: "List directory contents for Unix-filesystem objects.",
38 39
		ShortDescription: `
Retrieves the object named by <ipfs-or-ipns-path> and displays the
40
contents.
41

42 43 44
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.
45 46 47 48 49 50 51
`,
	},

	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) {
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
					link.Node, err = link.GetNode(ctx, node.DAG)
109 110 111 112 113 114 115 116 117
					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
					}
118
					t := d.GetType()
119 120 121
					lsLink := LsLink{
						Name: link.Name,
						Hash: link.Hash.B58String(),
122
						Type: t.String(),
123
					}
124
					if t == unixfspb.Data_File {
125 126 127 128
						lsLink.Size = d.GetFilesize()
					} else {
						lsLink.Size = link.Size
					}
129
					links[i] = lsLink
130
				}
Jeromy's avatar
Jeromy committed
131 132 133 134 135 136
			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
137 138 139
			}
		}

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

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

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

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