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

import (
	"bytes"
	"fmt"
	"io"
7
	"sort"
8 9 10
	"text/tabwriter"

	cmds "github.com/ipfs/go-ipfs/commands"
Jan Winkelmann's avatar
Jan Winkelmann committed
11
	e "github.com/ipfs/go-ipfs/core/commands/e"
12 13
	iface "github.com/ipfs/go-ipfs/core/coreapi/interface"

Hector Sanjuan's avatar
Hector Sanjuan committed
14
	merkledag "gx/ipfs/QmSei8kFMfqdJq7Q68d2LMnHbTWKKg2daA29ezUYFAUNgc/go-merkledag"
15
	cmdkit "gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
Hector Sanjuan's avatar
Hector Sanjuan committed
16
	unixfs "gx/ipfs/QmfB3oNXGGq9S4B2a9YeCajoATms3Zw2VvDm8fK7VeLSV8/go-unixfs"
17 18 19 20 21
)

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

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

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

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

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.
46 47 48

This functionality is deprecated, and will be removed in future versions. If
possible, please use 'ipfs ls' instead.
Richard Littauer's avatar
Richard Littauer committed
49 50 51
`,
		LongDescription: `
Displays the contents of an IPFS or IPNS object(s) at the given path.
52

53 54
The JSON output contains size information. For files, the child size
is the total size of the file contents. For directories, the child
55
size is the IPFS link size.
Richard Littauer's avatar
Richard Littauer committed
56 57 58 59 60 61 62 63 64 65

The path can be a prefixless ref; in this case, we assume it to be an
/ipfs ref and not /ipns.

Example:

    > ipfs file ls QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ
    cat.jpg
    > ipfs file ls /ipfs/QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ
    cat.jpg
66 67 68

This functionality is deprecated, and will be removed in future versions. If
possible, please use 'ipfs ls' instead.
69 70 71
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
72 73
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to list links from.").EnableStdin(),
74 75
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
76
		node, err := req.InvocContext().GetNode()
77
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
78
			res.SetError(err, cmdkit.ErrNormal)
79 80 81
			return
		}

82 83 84 85 86 87
		api, err := req.InvocContext().GetApi()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

88 89
		paths := req.Arguments()

90 91 92 93 94
		output := LsOutput{
			Arguments: map[string]string{},
			Objects:   map[string]*LsObject{},
		}

95
		for _, p := range paths {
Jeromy's avatar
Jeromy committed
96
			ctx := req.Context()
97

98 99 100 101
			fpath, err := iface.ParsePath(p)
			if err != nil {
				res.SetError(err, cmdkit.ErrNormal)
				return
102 103
			}

104
			merkleNode, err := api.ResolveNode(ctx, fpath)
105
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
106
				res.SetError(err, cmdkit.ErrNormal)
107 108 109
				return
			}

Jeromy's avatar
Jeromy committed
110
			c := merkleNode.Cid()
111

Jeromy's avatar
Jeromy committed
112
			hash := c.String()
113
			output.Arguments[p] = hash
114 115 116 117 118 119

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

120 121
			ndpb, ok := merkleNode.(*merkledag.ProtoNode)
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
122
				res.SetError(merkledag.ErrNotProtobuf, cmdkit.ErrNormal)
123 124 125
				return
			}

Overbool's avatar
Overbool committed
126
			unixFSNode, err := unixfs.FSNodeFromBytes(ndpb.Data())
127
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
128
				res.SetError(err, cmdkit.ErrNormal)
129 130
				return
			}
131

Overbool's avatar
Overbool committed
132
			t := unixFSNode.Type()
133 134

			output.Objects[hash] = &LsObject{
Jeromy's avatar
Jeromy committed
135
				Hash: c.String(),
136
				Type: t.String(),
Overbool's avatar
Overbool committed
137
				Size: unixFSNode.FileSize(),
138 139
			}

140
			switch t {
Overbool's avatar
Overbool committed
141
			case unixfs.TFile:
142
				break
Overbool's avatar
Overbool committed
143
			case unixfs.THAMTShard:
144 145 146
				// We need a streaming ls API for this.
				res.SetError(fmt.Errorf("cannot list large directories yet"), cmdkit.ErrNormal)
				return
Overbool's avatar
Overbool committed
147
			case unixfs.TDirectory:
148
				links := make([]LsLink, len(merkleNode.Links()))
149
				output.Objects[hash].Links = links
150 151
				for i, link := range merkleNode.Links() {
					linkNode, err := link.GetNode(ctx, node.DAG)
152
					if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
153
						res.SetError(err, cmdkit.ErrNormal)
154 155
						return
					}
156 157
					lnpb, ok := linkNode.(*merkledag.ProtoNode)
					if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
158
						res.SetError(merkledag.ErrNotProtobuf, cmdkit.ErrNormal)
159 160 161
						return
					}

Overbool's avatar
Overbool committed
162
					d, err := unixfs.FSNodeFromBytes(lnpb.Data())
163
					if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
164
						res.SetError(err, cmdkit.ErrNormal)
165 166
						return
					}
Overbool's avatar
Overbool committed
167
					t := d.Type()
168 169
					lsLink := LsLink{
						Name: link.Name,
170
						Hash: link.Cid.String(),
171
						Type: t.String(),
172
					}
Overbool's avatar
Overbool committed
173
					if t == unixfs.TFile {
Overbool's avatar
Overbool committed
174
						lsLink.Size = d.FileSize()
175 176 177
					} else {
						lsLink.Size = link.Size
					}
178
					links[i] = lsLink
179
				}
Overbool's avatar
Overbool committed
180
			case unixfs.TSymlink:
Jan Winkelmann's avatar
Jan Winkelmann committed
181
				res.SetError(fmt.Errorf("cannot list symlinks yet"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
182 183
				return
			default:
Jan Winkelmann's avatar
Jan Winkelmann committed
184
				res.SetError(fmt.Errorf("unrecognized type: %s", t), cmdkit.ErrImplementation)
Jeromy's avatar
Jeromy committed
185
				return
186 187 188
			}
		}

189
		res.SetOutput(&output)
190 191 192
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
193 194 195 196
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}
197

Jan Winkelmann's avatar
Jan Winkelmann committed
198 199 200 201
			output, ok := v.(*LsOutput)
			if !ok {
				return nil, e.TypeErr(output, v)
			}
202 203
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
204 205 206 207 208 209 210 211 212

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

213
				if object.Type == "Directory" {
214
					directories = append(directories, argument)
215 216
				} else {
					nonDirectories = append(nonDirectories, argument)
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
				}
			}
			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)
				}
238 239 240 241 242
				if len(output.Arguments) > 1 {
					for _, arg := range directories[i:] {
						if output.Arguments[arg] == hash {
							fmt.Fprintf(w, "%s:\n", arg)
						}
243
					}
244 245 246 247 248 249 250 251 252 253 254 255
				}
				for _, link := range object.Links {
					fmt.Fprintf(w, "%s\n", link.Name)
				}
			}
			w.Flush()

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