ls.go 5.14 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
			unixFSNode, err := unixfs.FromBytes(merkleNode.Data())
107 108 109 110
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
111 112

			t := unixFSNode.GetType()
113 114

			output.Objects[hash] = &LsObject{
Jeromy's avatar
Jeromy committed
115
				Hash: c.String(),
116 117 118 119
				Type: t.String(),
				Size: unixFSNode.GetFilesize(),
			}

120 121
			switch t {
			case unixfspb.Data_File:
122
				break
123
			case unixfspb.Data_Directory:
124 125 126
				links := make([]LsLink, len(merkleNode.Links))
				output.Objects[hash].Links = links
				for i, link := range merkleNode.Links {
127 128
					var linkNode *merkledag.Node
					linkNode, err = link.GetNode(ctx, node.DAG)
129 130 131 132
					if err != nil {
						res.SetError(err, cmds.ErrNormal)
						return
					}
133
					d, err := unixfs.FromBytes(linkNode.Data())
134 135 136 137
					if err != nil {
						res.SetError(err, cmds.ErrNormal)
						return
					}
138
					t := d.GetType()
139 140 141
					lsLink := LsLink{
						Name: link.Name,
						Hash: link.Hash.B58String(),
142
						Type: t.String(),
143
					}
144
					if t == unixfspb.Data_File {
145 146 147 148
						lsLink.Size = d.GetFilesize()
					} else {
						lsLink.Size = link.Size
					}
149
					links[i] = lsLink
150
				}
Jeromy's avatar
Jeromy committed
151 152 153 154 155 156
			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
157 158 159
			}
		}

160
		res.SetOutput(&output)
161 162 163 164 165 166 167
	},
	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)
168 169 170 171 172 173 174 175 176

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

177
				if object.Type == "Directory" {
178
					directories = append(directories, argument)
179 180
				} else {
					nonDirectories = append(nonDirectories, argument)
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
				}
			}
			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)
				}
202 203 204 205 206
				if len(output.Arguments) > 1 {
					for _, arg := range directories[i:] {
						if output.Arguments[arg] == hash {
							fmt.Fprintf(w, "%s:\n", arg)
						}
207
					}
208 209 210 211 212 213 214 215 216 217 218 219
				}
				for _, link := range object.Links {
					fmt.Fprintf(w, "%s\n", link.Name)
				}
			}
			w.Flush()

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