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

import (
	"fmt"
	"io"
6
	"sort"
7 8
	"text/tabwriter"

Overbool's avatar
Overbool committed
9
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
10

Jakub Sztandera's avatar
Jakub Sztandera committed
11 12 13 14
	cmdkit "github.com/ipfs/go-ipfs-cmdkit"
	cmds "github.com/ipfs/go-ipfs-cmds"
	merkledag "github.com/ipfs/go-merkledag"
	unixfs "github.com/ipfs/go-unixfs"
15
	path "github.com/ipfs/interface-go-ipfs-core/path"
16 17 18 19 20
)

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
}

var LsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
37
	Helptext: cmdkit.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
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
71 72
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to list links from.").EnableStdin(),
73
	},
Overbool's avatar
Overbool committed
74 75
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
76
		if err != nil {
Overbool's avatar
Overbool committed
77
			return err
78 79
		}

80
		api, err := cmdenv.GetApi(env, req)
81
		if err != nil {
Overbool's avatar
Overbool committed
82
			return err
83 84
		}

Overbool's avatar
Overbool committed
85 86 87 88 89
		if err := req.ParseBodyArgs(); err != nil {
			return err
		}

		paths := req.Arguments
90

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

96
		for _, p := range paths {
Overbool's avatar
Overbool committed
97
			ctx := req.Context
98

99
			merkleNode, err := api.ResolveNode(ctx, path.New(p))
100
			if err != nil {
Overbool's avatar
Overbool committed
101
				return err
102 103
			}

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

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

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

114 115
			ndpb, ok := merkleNode.(*merkledag.ProtoNode)
			if !ok {
Overbool's avatar
Overbool committed
116
				return merkledag.ErrNotProtobuf
117 118
			}

Overbool's avatar
Overbool committed
119
			unixFSNode, err := unixfs.FSNodeFromBytes(ndpb.Data())
120
			if err != nil {
Overbool's avatar
Overbool committed
121
				return err
122
			}
123

Overbool's avatar
Overbool committed
124
			t := unixFSNode.Type()
125 126

			output.Objects[hash] = &LsObject{
Jeromy's avatar
Jeromy committed
127
				Hash: c.String(),
128
				Type: t.String(),
Overbool's avatar
Overbool committed
129
				Size: unixFSNode.FileSize(),
130 131
			}

132
			switch t {
Overbool's avatar
Overbool committed
133
			case unixfs.TFile:
134
				break
Overbool's avatar
Overbool committed
135
			case unixfs.THAMTShard:
136
				// We need a streaming ls API for this.
Overbool's avatar
Overbool committed
137
				return fmt.Errorf("cannot list large directories yet")
Overbool's avatar
Overbool committed
138
			case unixfs.TDirectory:
139
				links := make([]LsLink, len(merkleNode.Links()))
140
				output.Objects[hash].Links = links
141
				for i, link := range merkleNode.Links() {
Overbool's avatar
Overbool committed
142
					linkNode, err := link.GetNode(ctx, nd.DAG)
143
					if err != nil {
Overbool's avatar
Overbool committed
144
						return err
145
					}
146 147
					lnpb, ok := linkNode.(*merkledag.ProtoNode)
					if !ok {
Overbool's avatar
Overbool committed
148
						return merkledag.ErrNotProtobuf
149 150
					}

Overbool's avatar
Overbool committed
151
					d, err := unixfs.FSNodeFromBytes(lnpb.Data())
152
					if err != nil {
Overbool's avatar
Overbool committed
153
						return err
154
					}
Overbool's avatar
Overbool committed
155
					t := d.Type()
156 157
					lsLink := LsLink{
						Name: link.Name,
158
						Hash: link.Cid.String(),
159
						Type: t.String(),
160
					}
Overbool's avatar
Overbool committed
161
					if t == unixfs.TFile {
Overbool's avatar
Overbool committed
162
						lsLink.Size = d.FileSize()
163 164 165
					} else {
						lsLink.Size = link.Size
					}
166
					links[i] = lsLink
167
				}
Overbool's avatar
Overbool committed
168
			case unixfs.TSymlink:
Overbool's avatar
Overbool committed
169
				return fmt.Errorf("cannot list symlinks yet")
Jeromy's avatar
Jeromy committed
170
			default:
Overbool's avatar
Overbool committed
171
				return fmt.Errorf("unrecognized type: %s", t)
172 173 174
			}
		}

Overbool's avatar
Overbool committed
175
		return cmds.EmitOnce(res, &output)
176
	},
Overbool's avatar
Overbool committed
177 178 179
	Encoders: cmds.EncoderMap{
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *LsOutput) error {
			tw := tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
180 181 182

			nonDirectories := []string{}
			directories := []string{}
Overbool's avatar
Overbool committed
183 184
			for argument, hash := range out.Arguments {
				object, ok := out.Objects[hash]
185
				if !ok {
Overbool's avatar
Overbool committed
186
					return fmt.Errorf("unresolved hash: %s", hash)
187 188
				}

189
				if object.Type == "Directory" {
190
					directories = append(directories, argument)
191 192
				} else {
					nonDirectories = append(nonDirectories, argument)
193 194 195 196 197 198
				}
			}
			sort.Strings(nonDirectories)
			sort.Strings(directories)

			for _, argument := range nonDirectories {
Overbool's avatar
Overbool committed
199
				fmt.Fprintf(tw, "%s\n", argument)
200 201 202 203
			}

			seen := map[string]bool{}
			for i, argument := range directories {
Overbool's avatar
Overbool committed
204
				hash := out.Arguments[argument]
205 206 207 208 209
				if _, ok := seen[hash]; ok {
					continue
				}
				seen[hash] = true

Overbool's avatar
Overbool committed
210
				object := out.Objects[hash]
211
				if i > 0 || len(nonDirectories) > 0 {
Overbool's avatar
Overbool committed
212
					fmt.Fprintln(tw)
213
				}
Overbool's avatar
Overbool committed
214
				if len(out.Arguments) > 1 {
215
					for _, arg := range directories[i:] {
Overbool's avatar
Overbool committed
216 217
						if out.Arguments[arg] == hash {
							fmt.Fprintf(tw, "%s:\n", arg)
218
						}
219
					}
220 221
				}
				for _, link := range object.Links {
Overbool's avatar
Overbool committed
222
					fmt.Fprintf(tw, "%s\n", link.Name)
223 224
				}
			}
Overbool's avatar
Overbool committed
225
			tw.Flush()
226

Overbool's avatar
Overbool committed
227 228
			return nil
		}),
229 230 231
	},
	Type: LsOutput{},
}