refs.go 6.84 KB
Newer Older
1 2 3
package commands

import (
4
	"bytes"
5
	"context"
Jeromy's avatar
Jeromy committed
6
	"errors"
7
	"io"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
8
	"strings"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
9

10 11
	cmds "github.com/ipfs/go-ipfs/commands"
	"github.com/ipfs/go-ipfs/core"
Jan Winkelmann's avatar
Jan Winkelmann committed
12
	e "github.com/ipfs/go-ipfs/core/commands/e"
13
	path "github.com/ipfs/go-ipfs/path"
Jeromy's avatar
Jeromy committed
14

Steven Allen's avatar
Steven Allen committed
15
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
16
	"gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
17
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
18 19
)

20 21
// KeyList is a general type for outputting lists of keys
type KeyList struct {
22
	Keys []*cid.Cid
23 24 25
}

// KeyListTextMarshaler outputs a KeyList as plaintext, one key per line
26
func KeyListTextMarshaler(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
27 28 29 30 31 32 33 34 35 36
	out, err := unwrapOutput(res.Output())
	if err != nil {
		return nil, err
	}

	output, ok := out.(*KeyList)
	if !ok {
		return nil, e.TypeErr(output, out)
	}

37
	buf := new(bytes.Buffer)
38
	for _, key := range output.Keys {
39
		buf.WriteString(key.String() + "\n")
40
	}
41
	return buf, nil
42 43
}

44
var RefsCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
45
	Helptext: cmdkit.HelpText{
46
		Tagline: "List links (references) from an object.",
47
		ShortDescription: `
48 49
Lists the hashes of all the links an IPFS or IPNS object(s) contains,
with the following format:
50

51 52
  <link base58 hash>

53
NOTE: List all references recursively by using the flag '-r'.
54 55
`,
	},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56 57 58
	Subcommands: map[string]*cmds.Command{
		"local": RefsLocalCmd,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
59 60
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ipfs-path", true, true, "Path to the object(s) to list refs from.").EnableStdin(),
61
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
62
	Options: []cmdkit.Option{
63
		cmdkit.StringOption("format", "Emit edges with given format. Available tokens: <src> <dst> <linkname>.").WithDefault("<dst>"),
64 65 66
		cmdkit.BoolOption("edges", "e", "Emit edge format: `<from> -> <to>`."),
		cmdkit.BoolOption("unique", "u", "Omit duplicate refs from output."),
		cmdkit.BoolOption("recursive", "r", "Recursively list links of child nodes."),
67
	},
68
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
69 70
		ctx := req.Context()
		n, err := req.InvocContext().GetNode()
71
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
72
			res.SetError(err, cmdkit.ErrNormal)
73
			return
74
		}
75

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
76
		unique, _, err := req.Option("unique").Bool()
77
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
78
			res.SetError(err, cmdkit.ErrNormal)
79
			return
80
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
81 82 83

		recursive, _, err := req.Option("recursive").Bool()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
84
			res.SetError(err, cmdkit.ErrNormal)
85
			return
86 87
		}

88
		format, _, err := req.Option("format").String()
89
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
90
			res.SetError(err, cmdkit.ErrNormal)
91
			return
92
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93

94
		edges, _, err := req.Option("edges").Bool()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
95
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
96
			res.SetError(err, cmdkit.ErrNormal)
97
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98
		}
99 100
		if edges {
			if format != "<dst>" {
Łukasz Magiera's avatar
Łukasz Magiera committed
101
				res.SetError(errors.New("using format argument with edges is not allowed"),
Jan Winkelmann's avatar
Jan Winkelmann committed
102
					cmdkit.ErrClient)
103 104 105 106 107
				return
			}

			format = "<src> -> <dst>"
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108

109
		objs, err := objectsForPaths(ctx, n, req.Arguments())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
110
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
111
			res.SetError(err, cmdkit.ErrNormal)
112
			return
113
		}
114

Jeromy's avatar
Jeromy committed
115 116
		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
117 118

		go func() {
Jeromy's avatar
Jeromy committed
119
			defer close(out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
120 121

			rw := RefWriter{
Jeromy's avatar
Jeromy committed
122
				out:       out,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123
				DAG:       n.DAG,
124
				Ctx:       ctx,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
125
				Unique:    unique,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126
				PrintFmt:  format,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
127 128 129 130 131
				Recursive: recursive,
			}

			for _, o := range objs {
				if _, err := rw.WriteRefs(o); err != nil {
forstmeier's avatar
forstmeier committed
132 133 134 135
					select {
					case out <- &RefWrapper{Err: err.Error()}:
					case <-ctx.Done():
					}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
136 137 138 139
					return
				}
			}
		}()
Jeromy's avatar
Jeromy committed
140
	},
Jakub Sztandera's avatar
Jakub Sztandera committed
141 142
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143 144 145
}

var RefsLocalCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
146
	Helptext: cmdkit.HelpText{
147
		Tagline: "List all local references.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
148 149 150 151 152
		ShortDescription: `
Displays the hashes of all local objects.
`,
	},

153
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
154 155
		ctx := req.Context()
		n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
157
			res.SetError(err, cmdkit.ErrNormal)
158
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159 160 161
		}

		// todo: make async
162
		allKeys, err := n.Blockstore.AllKeysChan(ctx)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
164
			res.SetError(err, cmdkit.ErrNormal)
165
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166 167
		}

Jakub Sztandera's avatar
Jakub Sztandera committed
168 169
		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170 171

		go func() {
Jakub Sztandera's avatar
Jakub Sztandera committed
172
			defer close(out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
173

174
			for k := range allKeys {
forstmeier's avatar
forstmeier committed
175 176 177 178 179
				select {
				case out <- &RefWrapper{Ref: k.String()}:
				case <-req.Context().Done():
					return
				}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
180 181
			}
		}()
Jakub Sztandera's avatar
Jakub Sztandera committed
182 183 184 185 186 187 188
	},
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
}

var refsMarshallerMap = cmds.MarshalerMap{
	cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
189 190 191
		v, err := unwrapOutput(res.Output())
		if err != nil {
			return nil, err
Jakub Sztandera's avatar
Jakub Sztandera committed
192 193
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
194 195 196 197
		obj, ok := v.(*RefWrapper)
		if !ok {
			return nil, e.TypeErr(obj, v)
		}
Jakub Sztandera's avatar
Jakub Sztandera committed
198

Jan Winkelmann's avatar
Jan Winkelmann committed
199 200
		if obj.Err != "" {
			return nil, errors.New(obj.Err)
Jakub Sztandera's avatar
Jakub Sztandera committed
201
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
202

Jan Winkelmann's avatar
Jan Winkelmann committed
203
		return strings.NewReader(obj.Ref + "\n"), nil
204 205 206
	},
}

207 208
func objectsForPaths(ctx context.Context, n *core.IpfsNode, paths []string) ([]ipld.Node, error) {
	objects := make([]ipld.Node, len(paths))
209 210 211 212 213 214 215
	for i, sp := range paths {
		p, err := path.ParsePath(sp)
		if err != nil {
			return nil, err
		}

		o, err := core.Resolve(ctx, n.Namesys, n.Resolver, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
216 217 218 219 220 221 222 223
		if err != nil {
			return nil, err
		}
		objects[i] = o
	}
	return objects, nil
}

Jeromy's avatar
Jeromy committed
224 225 226
type RefWrapper struct {
	Ref string
	Err string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
227 228 229
}

type RefWriter struct {
Jeromy's avatar
Jeromy committed
230
	out chan interface{}
231
	DAG ipld.DAGService
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
232
	Ctx context.Context
233

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
234 235
	Unique    bool
	Recursive bool
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
236
	PrintFmt  string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
237

Jeromy's avatar
Jeromy committed
238
	seen *cid.Set
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
239 240 241
}

// WriteRefs writes refs of the given object to the underlying writer.
242
func (rw *RefWriter) WriteRefs(n ipld.Node) (int, error) {
243 244 245
	if rw.Recursive {
		return rw.writeRefsRecursive(n)
	}
rht's avatar
rht committed
246
	return rw.writeRefsSingle(n)
247 248
}

249
func (rw *RefWriter) writeRefsRecursive(n ipld.Node) (int, error) {
Jeromy's avatar
Jeromy committed
250
	nc := n.Cid()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
251

252
	var count int
253
	for i, ng := range ipld.GetDAG(rw.Ctx, rw.DAG, n) {
254
		lc := n.Links()[i].Cid
Jeromy's avatar
Jeromy committed
255
		if rw.skip(lc) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
256 257 258
			continue
		}

259
		if err := rw.WriteEdge(nc, lc, n.Links()[i].Name); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
260 261
			return count, err
		}
262

263
		nd, err := ng.Get(rw.Ctx)
264
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
265
			return count, err
266 267
		}

268
		c, err := rw.writeRefsRecursive(nd)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
269
		count += c
270
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
271
			return count, err
272 273
		}
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
274
	return count, nil
275 276
}

277
func (rw *RefWriter) writeRefsSingle(n ipld.Node) (int, error) {
Jeromy's avatar
Jeromy committed
278
	c := n.Cid()
279

Jeromy's avatar
Jeromy committed
280
	if rw.skip(c) {
281 282 283 284
		return 0, nil
	}

	count := 0
285 286
	for _, l := range n.Links() {
		lc := l.Cid
Jeromy's avatar
Jeromy committed
287
		if rw.skip(lc) {
288 289 290
			continue
		}

Jeromy's avatar
Jeromy committed
291
		if err := rw.WriteEdge(c, lc, l.Name); err != nil {
292 293 294 295 296 297 298
			return count, err
		}
		count++
	}
	return count, nil
}

Jeromy's avatar
Jeromy committed
299 300
// skip returns whether to skip a cid
func (rw *RefWriter) skip(c *cid.Cid) bool {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
301 302 303
	if !rw.Unique {
		return false
	}
304

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
305
	if rw.seen == nil {
Jeromy's avatar
Jeromy committed
306
		rw.seen = cid.NewSet()
307 308
	}

Jeromy's avatar
Jeromy committed
309 310 311
	has := rw.seen.Has(c)
	if !has {
		rw.seen.Add(c)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
312
	}
Jeromy's avatar
Jeromy committed
313
	return has
314 315
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
316
// Write one edge
Jeromy's avatar
Jeromy committed
317
func (rw *RefWriter) WriteEdge(from, to *cid.Cid, linkname string) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
318 319 320 321 322
	if rw.Ctx != nil {
		select {
		case <-rw.Ctx.Done(): // just in case.
			return rw.Ctx.Err()
		default:
323 324 325
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
326
	var s string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
327 328 329
	switch {
	case rw.PrintFmt != "":
		s = rw.PrintFmt
Jeromy's avatar
Jeromy committed
330 331
		s = strings.Replace(s, "<src>", from.String(), -1)
		s = strings.Replace(s, "<dst>", to.String(), -1)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
332 333
		s = strings.Replace(s, "<linkname>", linkname, -1)
	default:
Jeromy's avatar
Jeromy committed
334
		s += to.String()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
335 336
	}

Jeromy's avatar
Jeromy committed
337
	rw.out <- &RefWrapper{Ref: s}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
338
	return nil
339
}