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

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

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
62 63 64 65
`,
	},

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

		paths := req.Arguments()

77 78 79 80 81 82
		output := LsOutput{
			Arguments: map[string]string{},
			Objects:   map[string]*LsObject{},
		}

		for _, fpath := range paths {
Jeromy's avatar
Jeromy committed
83
			ctx := req.Context()
84
			merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
85 86 87 88 89
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}

90
			key, err := merkleNode.Key()
91 92 93
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
94
			}
95

96 97 98 99 100 101 102 103 104 105 106 107 108
			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
			}
109 110

			t := unixFSNode.GetType()
111 112 113 114 115 116 117

			output.Objects[hash] = &LsObject{
				Hash: key.String(),
				Type: t.String(),
				Size: unixFSNode.GetFilesize(),
			}

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

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

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

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

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