ls.go 5.37 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"
Richard Littauer's avatar
Richard Littauer committed
12
	merkledag "github.com/ipfs/go-ipfs/merkledag"
13 14 15 16 17 18 19 20
	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
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 41 42 43 44
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.
45 46 47

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

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

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
65 66 67

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

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

		paths := req.Arguments()

83 84 85 86 87 88
		output := LsOutput{
			Arguments: map[string]string{},
			Objects:   map[string]*LsObject{},
		}

		for _, fpath := range paths {
Jeromy's avatar
Jeromy committed
89
			ctx := req.Context()
90
			merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
91 92 93 94 95
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

Jeromy's avatar
Jeromy committed
96
			c := merkleNode.Cid()
97

Jeromy's avatar
Jeromy committed
98
			hash := c.String()
99 100 101 102 103 104 105
			output.Arguments[fpath] = hash

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

106 107 108 109 110 111 112
			ndpb, ok := merkleNode.(*merkledag.ProtoNode)
			if !ok {
				res.SetError(merkledag.ErrNotProtobuf, cmds.ErrNormal)
				return
			}

			unixFSNode, err := unixfs.FromBytes(ndpb.Data())
113 114 115 116
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
117 118

			t := unixFSNode.GetType()
119 120

			output.Objects[hash] = &LsObject{
Jeromy's avatar
Jeromy committed
121
				Hash: c.String(),
122 123 124 125
				Type: t.String(),
				Size: unixFSNode.GetFilesize(),
			}

126 127
			switch t {
			case unixfspb.Data_File:
128
				break
129
			case unixfspb.Data_Directory:
130
				links := make([]LsLink, len(merkleNode.Links()))
131
				output.Objects[hash].Links = links
132 133
				for i, link := range merkleNode.Links() {
					linkNode, err := link.GetNode(ctx, node.DAG)
134 135 136 137
					if err != nil {
						res.SetError(err, cmds.ErrNormal)
						return
					}
138 139 140 141 142 143 144
					lnpb, ok := linkNode.(*merkledag.ProtoNode)
					if !ok {
						res.SetError(merkledag.ErrNotProtobuf, cmds.ErrNormal)
						return
					}

					d, err := unixfs.FromBytes(lnpb.Data())
145 146 147 148
					if err != nil {
						res.SetError(err, cmds.ErrNormal)
						return
					}
149
					t := d.GetType()
150 151
					lsLink := LsLink{
						Name: link.Name,
152
						Hash: link.Cid.String(),
153
						Type: t.String(),
154
					}
155
					if t == unixfspb.Data_File {
156 157 158 159
						lsLink.Size = d.GetFilesize()
					} else {
						lsLink.Size = link.Size
					}
160
					links[i] = lsLink
161
				}
Jeromy's avatar
Jeromy committed
162 163 164 165 166 167
			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
168 169 170
			}
		}

171
		res.SetOutput(&output)
172 173 174 175 176 177 178
	},
	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)
179 180 181 182 183 184 185 186 187

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

188
				if object.Type == "Directory" {
189
					directories = append(directories, argument)
190 191
				} else {
					nonDirectories = append(nonDirectories, argument)
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
				}
			}
			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)
				}
213 214 215 216 217
				if len(output.Arguments) > 1 {
					for _, arg := range directories[i:] {
						if output.Arguments[arg] == hash {
							fmt.Fprintf(w, "%s:\n", arg)
						}
218
					}
219 220 221 222 223 224 225 226 227 228 229 230
				}
				for _, link := range object.Links {
					fmt.Fprintf(w, "%s\n", link.Name)
				}
			}
			w.Flush()

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