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

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

9
	key "github.com/ipfs/go-ipfs/blocks/key"
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"
14
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
Jeromy's avatar
Jeromy committed
15
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
16 17
)

18 19
// KeyList is a general type for outputting lists of keys
type KeyList struct {
20
	Keys []key.Key
21 22 23
}

// KeyListTextMarshaler outputs a KeyList as plaintext, one key per line
24
func KeyListTextMarshaler(res cmds.Response) (io.Reader, error) {
25
	output := res.Output().(*KeyList)
26
	buf := new(bytes.Buffer)
27
	for _, key := range output.Keys {
28
		buf.WriteString(key.B58String() + "\n")
29
	}
30
	return buf, nil
31 32
}

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

40 41
  <link base58 hash>

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

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

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

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

83
		edges, _, err := req.Option("edges").Bool()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
84
		if err != nil {
85 86
			res.SetError(err, cmds.ErrNormal)
			return
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
87
		}
88 89 90 91 92 93 94 95 96
		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
97

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

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

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

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

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

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

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

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

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

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

160
			for k := range allKeys {
Jakub Sztandera's avatar
Jakub Sztandera committed
161
				out <- &RefWrapper{Ref: k.B58String()}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
162 163
			}
		}()
Jakub Sztandera's avatar
Jakub Sztandera committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
	},
	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
188

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

197
func objectsForPaths(ctx context.Context, n *core.IpfsNode, paths []string) ([]*dag.Node, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
198 199
	objects := make([]*dag.Node, len(paths))
	for i, p := range paths {
200
		o, err := core.Resolve(ctx, n, path.Path(p))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
201 202 203 204 205 206 207 208
		if err != nil {
			return nil, err
		}
		objects[i] = o
	}
	return objects, nil
}

Jeromy's avatar
Jeromy committed
209 210 211
type RefWrapper struct {
	Ref string
	Err string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
212 213 214
}

type RefWriter struct {
Jeromy's avatar
Jeromy committed
215
	out chan interface{}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
216 217
	DAG dag.DAGService
	Ctx context.Context
218

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
219 220
	Unique    bool
	Recursive bool
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
221
	PrintFmt  string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
222

223
	seen map[key.Key]struct{}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
224 225 226 227
}

// WriteRefs writes refs of the given object to the underlying writer.
func (rw *RefWriter) WriteRefs(n *dag.Node) (int, error) {
228 229 230
	if rw.Recursive {
		return rw.writeRefsRecursive(n)
	}
rht's avatar
rht committed
231
	return rw.writeRefsSingle(n)
232 233 234
}

func (rw *RefWriter) writeRefsRecursive(n *dag.Node) (int, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
235 236 237 238 239
	nkey, err := n.Key()
	if err != nil {
		return 0, err
	}

240
	var count int
241
	for i, ng := range dag.GetDAG(rw.Ctx, rw.DAG, n) {
242
		lk := key.Key(n.Links[i].Hash)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
243 244 245 246
		if rw.skip(lk) {
			continue
		}

247
		if err := rw.WriteEdge(nkey, lk, n.Links[i].Name); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
248 249
			return count, err
		}
250

251
		nd, err := ng.Get(rw.Ctx)
252
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
253
			return count, err
254 255
		}

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

265 266 267 268 269 270 271 272 273 274 275 276
func (rw *RefWriter) writeRefsSingle(n *dag.Node) (int, error) {
	nkey, err := n.Key()
	if err != nil {
		return 0, err
	}

	if rw.skip(nkey) {
		return 0, nil
	}

	count := 0
	for _, l := range n.Links {
277
		lk := key.Key(l.Hash)
278 279 280 281 282 283 284 285 286 287 288 289 290

		if rw.skip(lk) {
			continue
		}

		if err := rw.WriteEdge(nkey, lk, l.Name); err != nil {
			return count, err
		}
		count++
	}
	return count, nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
291
// skip returns whether to skip a key
292
func (rw *RefWriter) skip(k key.Key) 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 {
298
		rw.seen = make(map[key.Key]struct{})
299 300
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
301 302 303 304 305
	_, found := rw.seen[k]
	if !found {
		rw.seen[k] = struct{}{}
	}
	return found
306 307
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
308
// Write one edge
309
func (rw *RefWriter) WriteEdge(from, to key.Key, 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
Michael Muré's avatar
Michael Muré committed
322 323
		s = strings.Replace(s, "<src>", from.B58String(), -1)
		s = strings.Replace(s, "<dst>", to.B58String(), -1)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
324 325
		s = strings.Replace(s, "<linkname>", linkname, -1)
	default:
Michael Muré's avatar
Michael Muré committed
326
		s += to.B58String()
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
}