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

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

	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
Jan Winkelmann's avatar
Jan Winkelmann committed
12
	e "github.com/ipfs/go-ipfs/core/commands/e"
Richard Littauer's avatar
Richard Littauer committed
13
	merkledag "github.com/ipfs/go-ipfs/merkledag"
14 15
	path "github.com/ipfs/go-ipfs/path"
	unixfs "github.com/ipfs/go-ipfs/unixfs"
16
	uio "github.com/ipfs/go-ipfs/unixfs/io"
17
	unixfspb "github.com/ipfs/go-ipfs/unixfs/pb"
18
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
19 20 21 22 23
)

type LsLink struct {
	Name, Hash string
	Size       uint64
24
	Type       string
25 26 27
}

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

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

var LsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
40
	Helptext: cmdkit.HelpText{
41
		Tagline: "List directory contents for Unix filesystem objects.",
42
		ShortDescription: `
Richard Littauer's avatar
Richard Littauer committed
43 44 45 46 47
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.
48 49 50

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
51 52 53
`,
		LongDescription: `
Displays the contents of an IPFS or IPNS object(s) at the given path.
54

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

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
68 69 70

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

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

		paths := req.Arguments()

86 87 88 89 90 91
		output := LsOutput{
			Arguments: map[string]string{},
			Objects:   map[string]*LsObject{},
		}

		for _, fpath := range paths {
Jeromy's avatar
Jeromy committed
92
			ctx := req.Context()
93 94 95 96 97 98 99

			resolver := &path.Resolver{
				DAG:         node.DAG,
				ResolveOnce: uio.ResolveUnixfsOnce,
			}

			merkleNode, err := core.Resolve(ctx, node.Namesys, resolver, path.Path(fpath))
100
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
101
				res.SetError(err, cmdkit.ErrNormal)
102 103 104
				return
			}

Jeromy's avatar
Jeromy committed
105
			c := merkleNode.Cid()
106

Jeromy's avatar
Jeromy committed
107
			hash := c.String()
108 109 110 111 112 113 114
			output.Arguments[fpath] = hash

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

115 116
			ndpb, ok := merkleNode.(*merkledag.ProtoNode)
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
117
				res.SetError(merkledag.ErrNotProtobuf, cmdkit.ErrNormal)
118 119 120 121
				return
			}

			unixFSNode, err := unixfs.FromBytes(ndpb.Data())
122
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
123
				res.SetError(err, cmdkit.ErrNormal)
124 125
				return
			}
126 127

			t := unixFSNode.GetType()
128 129

			output.Objects[hash] = &LsObject{
Jeromy's avatar
Jeromy committed
130
				Hash: c.String(),
131 132 133 134
				Type: t.String(),
				Size: unixFSNode.GetFilesize(),
			}

135 136
			switch t {
			case unixfspb.Data_File:
137
				break
138
			case unixfspb.Data_Directory:
139
				links := make([]LsLink, len(merkleNode.Links()))
140
				output.Objects[hash].Links = links
141 142
				for i, link := range merkleNode.Links() {
					linkNode, err := link.GetNode(ctx, node.DAG)
143
					if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
144
						res.SetError(err, cmdkit.ErrNormal)
145 146
						return
					}
147 148
					lnpb, ok := linkNode.(*merkledag.ProtoNode)
					if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
149
						res.SetError(merkledag.ErrNotProtobuf, cmdkit.ErrNormal)
150 151 152 153
						return
					}

					d, err := unixfs.FromBytes(lnpb.Data())
154
					if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
155
						res.SetError(err, cmdkit.ErrNormal)
156 157
						return
					}
158
					t := d.GetType()
159 160
					lsLink := LsLink{
						Name: link.Name,
161
						Hash: link.Cid.String(),
162
						Type: t.String(),
163
					}
164
					if t == unixfspb.Data_File {
165 166 167 168
						lsLink.Size = d.GetFilesize()
					} else {
						lsLink.Size = link.Size
					}
169
					links[i] = lsLink
170
				}
Jeromy's avatar
Jeromy committed
171
			case unixfspb.Data_Symlink:
Jan Winkelmann's avatar
Jan Winkelmann committed
172
				res.SetError(fmt.Errorf("cannot list symlinks yet"), cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
173 174
				return
			default:
Jan Winkelmann's avatar
Jan Winkelmann committed
175
				res.SetError(fmt.Errorf("unrecognized type: %s", t), cmdkit.ErrImplementation)
Jeromy's avatar
Jeromy committed
176
				return
177 178 179
			}
		}

180
		res.SetOutput(&output)
181 182 183
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
184 185 186 187
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}
188

Jan Winkelmann's avatar
Jan Winkelmann committed
189 190 191 192
			output, ok := v.(*LsOutput)
			if !ok {
				return nil, e.TypeErr(output, v)
			}
193 194
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
195 196 197 198 199 200 201 202 203

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

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

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