refs.go 6.77 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 12 13
	cmds "github.com/ipfs/go-ipfs/commands"
	"github.com/ipfs/go-ipfs/core"
	dag "github.com/ipfs/go-ipfs/merkledag"
	path "github.com/ipfs/go-ipfs/path"
Jeromy's avatar
Jeromy committed
14

15 16 17
	u "gx/ipfs/QmSU6eubNdhXjFBJBSksTp8kv8YRub8mGAPv8tVJHmL2EU/go-ipfs-util"
	cid "gx/ipfs/QmTprEaAA2A9bst5XH7exuyi5KzNMK3SEDNN8rBDnKWcUS/go-cid"
	node "gx/ipfs/QmYNyRZJBUYPNrLszFmrBrPJbsBh2vMsefz5gnDpB5M1P6/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) {
27
	output := res.Output().(*KeyList)
28
	buf := new(bytes.Buffer)
29
	for _, key := range output.Keys {
30
		buf.WriteString(key.String() + "\n")
31
	}
32
	return buf, nil
33 34
}

35
var RefsCmd = &cmds.Command{
36
	Helptext: cmds.HelpText{
37
		Tagline: "List links (references) from an object.",
38
		ShortDescription: `
39 40
Lists the hashes of all the links an IPFS or IPNS object(s) contains,
with the following format:
41

42 43
  <link base58 hash>

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
67
		unique, _, err := req.Option("unique").Bool()
68
		if err != nil {
69 70
			res.SetError(err, cmds.ErrNormal)
			return
71
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
72 73 74

		recursive, _, err := req.Option("recursive").Bool()
		if err != nil {
75 76
			res.SetError(err, cmds.ErrNormal)
			return
77 78
		}

79
		format, _, err := req.Option("format").String()
80
		if err != nil {
81 82
			res.SetError(err, cmds.ErrNormal)
			return
83
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
84

85
		edges, _, err := req.Option("edges").Bool()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86
		if err != nil {
87 88
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
89
		}
90 91 92 93 94 95 96 97 98
		if edges {
			if format != "<dst>" {
				res.SetError(errors.New("using format arguement with edges is not allowed"),
					cmds.ErrClient)
				return
			}

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

100
		objs, err := objectsForPaths(ctx, n, req.Arguments())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101
		if err != nil {
102 103
			res.SetError(err, cmds.ErrNormal)
			return
104
		}
105

Jeromy's avatar
Jeromy committed
106 107
		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108 109

		go func() {
Jeromy's avatar
Jeromy committed
110
			defer close(out)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
111 112

			rw := RefWriter{
Jeromy's avatar
Jeromy committed
113
				out:       out,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
114
				DAG:       n.DAG,
115
				Ctx:       ctx,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
116
				Unique:    unique,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
117
				PrintFmt:  format,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
118 119 120 121 122
				Recursive: recursive,
			}

			for _, o := range objs {
				if _, err := rw.WriteRefs(o); err != nil {
Jeromy's avatar
Jeromy committed
123
					out <- &RefWrapper{Err: err.Error()}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124 125 126 127
					return
				}
			}
		}()
Jeromy's avatar
Jeromy committed
128
	},
Jakub Sztandera's avatar
Jakub Sztandera committed
129 130
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
131 132 133 134
}

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

141
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
142 143
		ctx := req.Context()
		n, err := req.InvocContext().GetNode()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
144
		if err != nil {
145 146
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
147 148 149
		}

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

Jakub Sztandera's avatar
Jakub Sztandera committed
156 157
		out := make(chan interface{})
		res.SetOutput((<-chan interface{})(out))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
158 159

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

162
			for k := range allKeys {
163
				out <- &RefWrapper{Ref: k.String()}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
164 165
			}
		}()
Jakub Sztandera's avatar
Jakub Sztandera committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
	},
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
}

var refsMarshallerMap = cmds.MarshalerMap{
	cmds.Text: func(res cmds.Response) (io.Reader, error) {
		outChan, ok := res.Output().(<-chan interface{})
		if !ok {
			return nil, u.ErrCast()
		}

		marshal := func(v interface{}) (io.Reader, error) {
			obj, ok := v.(*RefWrapper)
			if !ok {
				return nil, u.ErrCast()
			}

			if obj.Err != "" {
				return nil, errors.New(obj.Err)
			}

			return strings.NewReader(obj.Ref + "\n"), nil
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190

Jakub Sztandera's avatar
Jakub Sztandera committed
191 192 193 194 195
		return &cmds.ChannelMarshaler{
			Channel:   outChan,
			Marshaler: marshal,
			Res:       res,
		}, nil
196 197 198
	},
}

Jeromy's avatar
Jeromy committed
199 200
func objectsForPaths(ctx context.Context, n *core.IpfsNode, paths []string) ([]node.Node, error) {
	objects := make([]node.Node, len(paths))
201 202 203 204 205 206 207
	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
208 209 210 211 212 213 214 215
		if err != nil {
			return nil, err
		}
		objects[i] = o
	}
	return objects, nil
}

Jeromy's avatar
Jeromy committed
216 217 218
type RefWrapper struct {
	Ref string
	Err string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
219 220 221
}

type RefWriter struct {
Jeromy's avatar
Jeromy committed
222
	out chan interface{}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
223 224
	DAG dag.DAGService
	Ctx context.Context
225

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
226 227
	Unique    bool
	Recursive bool
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
228
	PrintFmt  string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
229

Jeromy's avatar
Jeromy committed
230
	seen *cid.Set
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
231 232 233
}

// WriteRefs writes refs of the given object to the underlying writer.
Jeromy's avatar
Jeromy committed
234
func (rw *RefWriter) WriteRefs(n node.Node) (int, error) {
235 236 237
	if rw.Recursive {
		return rw.writeRefsRecursive(n)
	}
rht's avatar
rht committed
238
	return rw.writeRefsSingle(n)
239 240
}

Jeromy's avatar
Jeromy committed
241
func (rw *RefWriter) writeRefsRecursive(n node.Node) (int, error) {
Jeromy's avatar
Jeromy committed
242
	nc := n.Cid()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
243

244
	var count int
245
	for i, ng := range dag.GetDAG(rw.Ctx, rw.DAG, n) {
246
		lc := n.Links()[i].Cid
Jeromy's avatar
Jeromy committed
247
		if rw.skip(lc) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
248 249 250
			continue
		}

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

255
		nd, err := ng.Get(rw.Ctx)
256
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
257
			return count, err
258 259
		}

260
		c, err := rw.writeRefsRecursive(nd)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
261
		count += c
262
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
263
			return count, err
264 265
		}
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
266
	return count, nil
267 268
}

Jeromy's avatar
Jeromy committed
269
func (rw *RefWriter) writeRefsSingle(n node.Node) (int, error) {
Jeromy's avatar
Jeromy committed
270
	c := n.Cid()
271

Jeromy's avatar
Jeromy committed
272
	if rw.skip(c) {
273 274 275 276
		return 0, nil
	}

	count := 0
277 278
	for _, l := range n.Links() {
		lc := l.Cid
Jeromy's avatar
Jeromy committed
279
		if rw.skip(lc) {
280 281 282
			continue
		}

Jeromy's avatar
Jeromy committed
283
		if err := rw.WriteEdge(c, lc, l.Name); err != nil {
284 285 286 287 288 289 290
			return count, err
		}
		count++
	}
	return count, nil
}

Jeromy's avatar
Jeromy committed
291 292
// skip returns whether to skip a cid
func (rw *RefWriter) skip(c *cid.Cid) bool {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
293 294 295
	if !rw.Unique {
		return false
	}
296

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
297
	if rw.seen == nil {
Jeromy's avatar
Jeromy committed
298
		rw.seen = cid.NewSet()
299 300
	}

Jeromy's avatar
Jeromy committed
301 302 303
	has := rw.seen.Has(c)
	if !has {
		rw.seen.Add(c)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
304
	}
Jeromy's avatar
Jeromy committed
305
	return has
306 307
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
308
// Write one edge
Jeromy's avatar
Jeromy committed
309
func (rw *RefWriter) WriteEdge(from, to *cid.Cid, linkname string) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
310 311 312 313 314
	if rw.Ctx != nil {
		select {
		case <-rw.Ctx.Done(): // just in case.
			return rw.Ctx.Err()
		default:
315 316 317
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
318
	var s string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
319 320 321
	switch {
	case rw.PrintFmt != "":
		s = rw.PrintFmt
Jeromy's avatar
Jeromy committed
322 323
		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
324 325
		s = strings.Replace(s, "<linkname>", linkname, -1)
	default:
Jeromy's avatar
Jeromy committed
326
		s += to.String()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
327 328
	}

Jeromy's avatar
Jeromy committed
329
	rw.out <- &RefWrapper{Ref: s}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
330
	return nil
331
}