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

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

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

Jakub Sztandera's avatar
Jakub Sztandera committed
12 13 14 15
	cid "github.com/ipfs/go-cid"
	cidenc "github.com/ipfs/go-cidutil/cidenc"
	cmds "github.com/ipfs/go-ipfs-cmds"
	ipld "github.com/ipfs/go-ipld-format"
16
	merkledag "github.com/ipfs/go-merkledag"
17 18
	iface "github.com/ipfs/interface-go-ipfs-core"
	path "github.com/ipfs/interface-go-ipfs-core/path"
19 20
)

Overbool's avatar
Overbool committed
21 22 23 24 25 26 27 28 29 30 31
var refsEncoderMap = cmds.EncoderMap{
	cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RefWrapper) error {
		if out.Err != "" {
			return fmt.Errorf(out.Err)
		}
		fmt.Fprintln(w, out.Ref)

		return nil
	}),
}

32 33
// KeyList is a general type for outputting lists of keys
type KeyList struct {
34
	Keys []cid.Cid
35 36
}

Kejie Zhang's avatar
Kejie Zhang committed
37 38 39 40 41 42 43 44
const (
	refsFormatOptionName    = "format"
	refsEdgesOptionName     = "edges"
	refsUniqueOptionName    = "unique"
	refsRecursiveOptionName = "recursive"
	refsMaxDepthOptionName  = "max-depth"
)

Overbool's avatar
Overbool committed
45
// RefsCmd is the `ipfs refs` command
46
var RefsCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
47
	Helptext: cmds.HelpText{
48
		Tagline: "List links (references) from an object.",
49
		ShortDescription: `
50 51
Lists the hashes of all the links an IPFS or IPNS object(s) contains,
with the following format:
52

53 54
  <link base58 hash>

55
NOTE: List all references recursively by using the flag '-r'.
56 57
`,
	},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
58 59 60
	Subcommands: map[string]*cmds.Command{
		"local": RefsLocalCmd,
	},
Steven Allen's avatar
Steven Allen committed
61 62
	Arguments: []cmds.Argument{
		cmds.StringArg("ipfs-path", true, true, "Path to the object(s) to list refs from.").EnableStdin(),
63
	},
Steven Allen's avatar
Steven Allen committed
64 65 66 67 68 69
	Options: []cmds.Option{
		cmds.StringOption(refsFormatOptionName, "Emit edges with given format. Available tokens: <src> <dst> <linkname>.").WithDefault("<dst>"),
		cmds.BoolOption(refsEdgesOptionName, "e", "Emit edge format: `<from> -> <to>`."),
		cmds.BoolOption(refsUniqueOptionName, "u", "Omit duplicate refs from output."),
		cmds.BoolOption(refsRecursiveOptionName, "r", "Recursively list links of child nodes."),
		cmds.IntOption(refsMaxDepthOptionName, "Only for recursive refs, limits fetch and listing to the given depth").WithDefault(-1),
70
	},
Overbool's avatar
Overbool committed
71
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
Overbool's avatar
Overbool committed
72 73 74 75 76
		err := req.ParseBodyArgs()
		if err != nil {
			return err
		}

Overbool's avatar
Overbool committed
77
		ctx := req.Context
78
		api, err := cmdenv.GetApi(env, req)
79
		if err != nil {
Overbool's avatar
Overbool committed
80
			return err
81
		}
82

83 84 85 86 87
		enc, err := cmdenv.GetCidEncoder(req)
		if err != nil {
			return err
		}

Overbool's avatar
Overbool committed
88 89 90 91 92
		unique, _ := req.Options[refsUniqueOptionName].(bool)
		recursive, _ := req.Options[refsRecursiveOptionName].(bool)
		maxDepth, _ := req.Options[refsMaxDepthOptionName].(int)
		edges, _ := req.Options[refsEdgesOptionName].(bool)
		format, _ := req.Options[refsFormatOptionName].(string)
Hector Sanjuan's avatar
Hector Sanjuan committed
93 94 95 96 97

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

98 99
		if edges {
			if format != "<dst>" {
Overbool's avatar
Overbool committed
100
				return errors.New("using format argument with edges is not allowed")
101 102 103 104
			}

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

106
		// TODO: use session for resolving as well.
107
		objs, err := objectsForPaths(ctx, api, req.Arguments)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108
		if err != nil {
Overbool's avatar
Overbool committed
109
			return err
110
		}
111

Overbool's avatar
Overbool committed
112 113
		rw := RefWriter{
			res:      res,
114
			DAG:      merkledag.NewSession(ctx, api.Dag()),
Overbool's avatar
Overbool committed
115 116 117 118 119
			Ctx:      ctx,
			Unique:   unique,
			PrintFmt: format,
			MaxDepth: maxDepth,
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
120

Overbool's avatar
Overbool committed
121
		for _, o := range objs {
122
			if _, err := rw.WriteRefs(o, enc); err != nil {
Overbool's avatar
Overbool committed
123 124
				if err := res.Emit(&RefWrapper{Err: err.Error()}); err != nil {
					return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
125 126
				}
			}
Overbool's avatar
Overbool committed
127
		}
Overbool's avatar
Overbool committed
128

Overbool's avatar
Overbool committed
129
		return nil
Overbool's avatar
Overbool committed
130
	},
Overbool's avatar
Overbool committed
131 132
	Encoders: refsEncoderMap,
	Type:     RefWrapper{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133 134 135
}

var RefsLocalCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
136
	Helptext: cmds.HelpText{
137
		Tagline: "List all local references.",
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
138 139 140 141 142
		ShortDescription: `
Displays the hashes of all local objects.
`,
	},

Overbool's avatar
Overbool committed
143 144 145
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		ctx := req.Context
		n, err := cmdenv.GetNode(env)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
146
		if err != nil {
Overbool's avatar
Overbool committed
147
			return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
148 149 150
		}

		// todo: make async
151
		allKeys, err := n.Blockstore.AllKeysChan(ctx)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
152
		if err != nil {
Overbool's avatar
Overbool committed
153
			return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
154 155
		}

Overbool's avatar
Overbool committed
156 157 158 159
		for k := range allKeys {
			err := res.Emit(&RefWrapper{Ref: k.String()})
			if err != nil {
				return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
160
			}
Overbool's avatar
Overbool committed
161
		}
Overbool's avatar
Overbool committed
162

Overbool's avatar
Overbool committed
163
		return nil
Overbool's avatar
Overbool committed
164
	},
Overbool's avatar
Overbool committed
165 166
	Encoders: refsEncoderMap,
	Type:     RefWrapper{},
167 168
}

169 170
func objectsForPaths(ctx context.Context, n iface.CoreAPI, paths []string) ([]cid.Cid, error) {
	roots := make([]cid.Cid, len(paths))
171
	for i, sp := range paths {
172
		o, err := n.ResolvePath(ctx, path.New(sp))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
173 174 175
		if err != nil {
			return nil, err
		}
176
		roots[i] = o.Cid()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
177
	}
178
	return roots, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179 180
}

Jeromy's avatar
Jeromy committed
181 182 183
type RefWrapper struct {
	Ref string
	Err string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184 185 186
}

type RefWriter struct {
Overbool's avatar
Overbool committed
187
	res cmds.ResponseEmitter
188
	DAG ipld.NodeGetter
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
189
	Ctx context.Context
190

Hector Sanjuan's avatar
Hector Sanjuan committed
191 192 193
	Unique   bool
	MaxDepth int
	PrintFmt string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
194

Hector Sanjuan's avatar
Hector Sanjuan committed
195
	seen map[string]int
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
196 197 198
}

// WriteRefs writes refs of the given object to the underlying writer.
199 200 201 202 203
func (rw *RefWriter) WriteRefs(c cid.Cid, enc cidenc.Encoder) (int, error) {
	n, err := rw.DAG.Get(rw.Ctx, c)
	if err != nil {
		return 0, err
	}
204
	return rw.writeRefsRecursive(n, 0, enc)
205 206
}

207
func (rw *RefWriter) writeRefsRecursive(n ipld.Node, depth int, enc cidenc.Encoder) (int, error) {
Jeromy's avatar
Jeromy committed
208
	nc := n.Cid()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
209

210
	var count int
211
	for i, ng := range ipld.GetDAG(rw.Ctx, rw.DAG, n) {
212
		lc := n.Links()[i].Cid
Hector Sanjuan's avatar
Hector Sanjuan committed
213 214 215 216 217 218 219 220 221 222
		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
223 224 225
			continue
		}

Hector Sanjuan's avatar
Hector Sanjuan committed
226 227 228 229
		// We must Get() the node because:
		// - it is new (never written)
		// - OR we need to go deeper.
		// This ensures printed refs are always fetched.
230
		nd, err := ng.Get(rw.Ctx)
231
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
232
			return count, err
233 234
		}

Hector Sanjuan's avatar
Hector Sanjuan committed
235 236
		// Write this node if not done before (or !Unique)
		if shouldWrite {
237
			if err := rw.WriteEdge(nc, lc, n.Links()[i].Name, enc); err != nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
238 239 240
				return count, err
			}
			count++
241 242
		}

Hector Sanjuan's avatar
Hector Sanjuan committed
243 244 245 246 247 248
		// 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 {
249
			c, err := rw.writeRefsRecursive(nd, depth+1, enc)
Hector Sanjuan's avatar
Hector Sanjuan committed
250 251 252 253
			count += c
			if err != nil {
				return count, err
			}
254 255
		}
	}
Hector Sanjuan's avatar
Hector Sanjuan committed
256

257 258 259
	return count, nil
}

Hector Sanjuan's avatar
Hector Sanjuan committed
260 261 262 263 264 265 266 267 268
// 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.
269
func (rw *RefWriter) visit(c cid.Cid, depth int) (bool, bool) {
Hector Sanjuan's avatar
Hector Sanjuan committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283
	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
284
	if !rw.Unique {
Hector Sanjuan's avatar
Hector Sanjuan committed
285
		return !atMaxDepth, true
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
286
	}
287

Hector Sanjuan's avatar
Hector Sanjuan committed
288 289
	// Unique == true from this point.
	// Thus, we keep track of seen Cids, and their depth.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
290
	if rw.seen == nil {
Hector Sanjuan's avatar
Hector Sanjuan committed
291
		rw.seen = make(map[string]int)
292
	}
Hector Sanjuan's avatar
Hector Sanjuan committed
293 294 295 296 297 298 299 300 301 302 303 304 305
	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
306
	}
Hector Sanjuan's avatar
Hector Sanjuan committed
307 308 309 310 311 312 313 314

	// 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
315 316
}

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

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

Overbool's avatar
Overbool committed
338
	return rw.res.Emit(&RefWrapper{Ref: s})
339
}