refs.go 9.37 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"
Jeromy's avatar
Jeromy committed
13

Steven Allen's avatar
Steven Allen committed
14
	path "gx/ipfs/QmNgXoHgXU1HzNb2HEZmRww9fDKE9NfDsvQwWLHiKHpvKM/go-path"
Hector Sanjuan's avatar
Hector Sanjuan committed
15
	cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
16 17
	ipld "gx/ipfs/QmX5CsuHyVZeTLxgRSYkgLSDQKb9UjE8xnhQzCEJWWWFsC/go-ipld-format"
	cid "gx/ipfs/QmZFbDTY9jfSBms2MchvYM9oYRbAF19K7Pby47yDBfpPrb/go-cid"
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."),
Hector Sanjuan's avatar
Hector Sanjuan committed
67
		cmdkit.IntOption("max-depth", "Only for recursive refs, limits fetch and listing to the given depth").WithDefault(-1),
68
	},
69
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
70 71
		ctx := req.Context()
		n, err := req.InvocContext().GetNode()
72
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
73
			res.SetError(err, cmdkit.ErrNormal)
74
			return
75
		}
76

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

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

Hector Sanjuan's avatar
Hector Sanjuan committed
89 90 91 92 93 94 95 96 97 98
		maxDepth, _, err := req.Option("max-depth").Int()
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		if !recursive {
			maxDepth = 1 // write only direct refs
		}

99
		format, _, err := req.Option("format").String()
100
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
101
			res.SetError(err, cmdkit.ErrNormal)
102
			return
103
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
104

105
		edges, _, err := req.Option("edges").Bool()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
106
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
107
			res.SetError(err, cmdkit.ErrNormal)
108
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
109
		}
110 111
		if edges {
			if format != "<dst>" {
Łukasz Magiera's avatar
Łukasz Magiera committed
112
				res.SetError(errors.New("using format argument with edges is not allowed"),
Jan Winkelmann's avatar
Jan Winkelmann committed
113
					cmdkit.ErrClient)
114 115 116 117 118
				return
			}

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

120
		objs, err := objectsForPaths(ctx, n, req.Arguments())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
121
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
122
			res.SetError(err, cmdkit.ErrNormal)
123
			return
124
		}
125

Jeromy's avatar
Jeromy committed
126 127
		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
128 129

		go func() {
Jeromy's avatar
Jeromy committed
130
			defer close(out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
131 132

			rw := RefWriter{
Hector Sanjuan's avatar
Hector Sanjuan committed
133 134 135 136 137 138
				out:      out,
				DAG:      n.DAG,
				Ctx:      ctx,
				Unique:   unique,
				PrintFmt: format,
				MaxDepth: maxDepth,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
139 140 141 142
			}

			for _, o := range objs {
				if _, err := rw.WriteRefs(o); err != nil {
forstmeier's avatar
forstmeier committed
143 144 145 146
					select {
					case out <- &RefWrapper{Err: err.Error()}:
					case <-ctx.Done():
					}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
147 148 149 150
					return
				}
			}
		}()
Jeromy's avatar
Jeromy committed
151
	},
Jakub Sztandera's avatar
Jakub Sztandera committed
152 153
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
154 155 156
}

var RefsLocalCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
157
	Helptext: cmdkit.HelpText{
158
		Tagline: "List all local references.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159 160 161 162 163
		ShortDescription: `
Displays the hashes of all local objects.
`,
	},

164
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
165 166
		ctx := req.Context()
		n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
167
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
168
			res.SetError(err, cmdkit.ErrNormal)
169
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170 171 172
		}

		// todo: make async
173
		allKeys, err := n.Blockstore.AllKeysChan(ctx)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
174
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
175
			res.SetError(err, cmdkit.ErrNormal)
176
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
177 178
		}

Jakub Sztandera's avatar
Jakub Sztandera committed
179 180
		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
181 182

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

185
			for k := range allKeys {
forstmeier's avatar
forstmeier committed
186 187 188 189 190
				select {
				case out <- &RefWrapper{Ref: k.String()}:
				case <-req.Context().Done():
					return
				}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
191 192
			}
		}()
Jakub Sztandera's avatar
Jakub Sztandera committed
193 194 195 196 197 198 199
	},
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
}

var refsMarshallerMap = cmds.MarshalerMap{
	cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
200 201 202
		v, err := unwrapOutput(res.Output())
		if err != nil {
			return nil, err
Jakub Sztandera's avatar
Jakub Sztandera committed
203 204
		}

Jan Winkelmann's avatar
Jan Winkelmann committed
205 206 207 208
		obj, ok := v.(*RefWrapper)
		if !ok {
			return nil, e.TypeErr(obj, v)
		}
Jakub Sztandera's avatar
Jakub Sztandera committed
209

Jan Winkelmann's avatar
Jan Winkelmann committed
210 211
		if obj.Err != "" {
			return nil, errors.New(obj.Err)
Jakub Sztandera's avatar
Jakub Sztandera committed
212
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
213

Jan Winkelmann's avatar
Jan Winkelmann committed
214
		return strings.NewReader(obj.Ref + "\n"), nil
215 216 217
	},
}

218 219
func objectsForPaths(ctx context.Context, n *core.IpfsNode, paths []string) ([]ipld.Node, error) {
	objects := make([]ipld.Node, len(paths))
220 221 222 223 224 225 226
	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
227 228 229 230 231 232 233 234
		if err != nil {
			return nil, err
		}
		objects[i] = o
	}
	return objects, nil
}

Jeromy's avatar
Jeromy committed
235 236 237
type RefWrapper struct {
	Ref string
	Err string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
238 239 240
}

type RefWriter struct {
Jeromy's avatar
Jeromy committed
241
	out chan interface{}
242
	DAG ipld.DAGService
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
243
	Ctx context.Context
244

Hector Sanjuan's avatar
Hector Sanjuan committed
245 246 247
	Unique   bool
	MaxDepth int
	PrintFmt string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
248

Hector Sanjuan's avatar
Hector Sanjuan committed
249
	seen map[string]int
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
250 251 252
}

// WriteRefs writes refs of the given object to the underlying writer.
253
func (rw *RefWriter) WriteRefs(n ipld.Node) (int, error) {
Hector Sanjuan's avatar
Hector Sanjuan committed
254 255
	return rw.writeRefsRecursive(n, 0)

256 257
}

Hector Sanjuan's avatar
Hector Sanjuan committed
258
func (rw *RefWriter) writeRefsRecursive(n ipld.Node, depth int) (int, error) {
Jeromy's avatar
Jeromy committed
259
	nc := n.Cid()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
260

261
	var count int
262
	for i, ng := range ipld.GetDAG(rw.Ctx, rw.DAG, n) {
263
		lc := n.Links()[i].Cid
Hector Sanjuan's avatar
Hector Sanjuan committed
264 265 266 267 268 269 270 271 272 273
		goDeeper, shouldWrite := rw.visit(lc, depth+1) // The children are at depth+1

		// Avoid "Get()" on the node and continue with next Link.
		// We can do this if:
		// - We printed it before (thus it was already seen and
		//   fetched with Get()
		// - AND we must not go deeper.
		// This is an optimization for pruned branches which have been
		// visited before.
		if !shouldWrite && !goDeeper {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
274 275 276
			continue
		}

Hector Sanjuan's avatar
Hector Sanjuan committed
277 278 279 280
		// We must Get() the node because:
		// - it is new (never written)
		// - OR we need to go deeper.
		// This ensures printed refs are always fetched.
281
		nd, err := ng.Get(rw.Ctx)
282
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
283
			return count, err
284 285
		}

Hector Sanjuan's avatar
Hector Sanjuan committed
286 287 288 289 290 291
		// Write this node if not done before (or !Unique)
		if shouldWrite {
			if err := rw.WriteEdge(nc, lc, n.Links()[i].Name); err != nil {
				return count, err
			}
			count++
292 293
		}

Hector Sanjuan's avatar
Hector Sanjuan committed
294 295 296 297 298 299 300 301 302 303 304
		// Keep going deeper. This happens:
		// - On unexplored branches
		// - On branches not explored deep enough
		// Note when !Unique, branches are always considered
		// unexplored and only depth limits apply.
		if goDeeper {
			c, err := rw.writeRefsRecursive(nd, depth+1)
			count += c
			if err != nil {
				return count, err
			}
305 306
		}
	}
Hector Sanjuan's avatar
Hector Sanjuan committed
307

308 309 310
	return count, nil
}

Hector Sanjuan's avatar
Hector Sanjuan committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
// visit returns two values:
// - the first boolean is true if we should keep traversing the DAG
// - the second boolean is true if we should print the CID
//
// visit will do branch pruning depending on rw.MaxDepth, previously visited
// cids and whether rw.Unique is set. i.e. rw.Unique = false and
// rw.MaxDepth = -1 disables any pruning. But setting rw.Unique to true will
// prune already visited branches at the cost of keeping as set of visited
// CIDs in memory.
func (rw *RefWriter) visit(c *cid.Cid, depth int) (bool, bool) {
	atMaxDepth := rw.MaxDepth >= 0 && depth == rw.MaxDepth
	overMaxDepth := rw.MaxDepth >= 0 && depth > rw.MaxDepth

	// Shortcut when we are over max depth. In practice, this
	// only applies when calling refs with --maxDepth=0, as root's
	// children are already over max depth. Otherwise nothing should
	// hit this.
	if overMaxDepth {
		return false, false
	}

	// We can shortcut right away if we don't need unique output:
	//   - we keep traversing when not atMaxDepth
	//   - always print
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
335
	if !rw.Unique {
Hector Sanjuan's avatar
Hector Sanjuan committed
336
		return !atMaxDepth, true
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
337
	}
338

Hector Sanjuan's avatar
Hector Sanjuan committed
339 340
	// Unique == true from this point.
	// Thus, we keep track of seen Cids, and their depth.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
341
	if rw.seen == nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
342
		rw.seen = make(map[string]int)
343
	}
Hector Sanjuan's avatar
Hector Sanjuan committed
344 345 346 347 348 349 350 351 352 353 354 355 356
	key := string(c.Bytes())
	oldDepth, ok := rw.seen[key]

	// Unique == true && depth < MaxDepth (or unlimited) from this point

	// Branch pruning cases:
	// - We saw the Cid before and either:
	//   - Depth is unlimited (MaxDepth = -1)
	//   - We saw it higher (smaller depth) in the DAG (means we must have
	//     explored deep enough before)
	// Because we saw the CID, we don't print it again.
	if ok && (rw.MaxDepth < 0 || oldDepth <= depth) {
		return false, false
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
357
	}
Hector Sanjuan's avatar
Hector Sanjuan committed
358 359 360 361 362 363 364 365

	// Final case, we must keep exploring the DAG from this CID
	// (unless we hit the depth limit).
	// We note down its depth because it was either not seen
	// or is lower than last time.
	// We print if it was not seen.
	rw.seen[key] = depth
	return !atMaxDepth, !ok
366 367
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
368
// Write one edge
Jeromy's avatar
Jeromy committed
369
func (rw *RefWriter) WriteEdge(from, to *cid.Cid, linkname string) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
370 371 372 373 374
	if rw.Ctx != nil {
		select {
		case <-rw.Ctx.Done(): // just in case.
			return rw.Ctx.Err()
		default:
375 376 377
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
378
	var s string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
379 380 381
	switch {
	case rw.PrintFmt != "":
		s = rw.PrintFmt
Jeromy's avatar
Jeromy committed
382 383
		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
384 385
		s = strings.Replace(s, "<linkname>", linkname, -1)
	default:
Jeromy's avatar
Jeromy committed
386
		s += to.String()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
387 388
	}

Jeromy's avatar
Jeromy committed
389
	rw.out <- &RefWrapper{Ref: s}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
390
	return nil
391
}