dag.go 6.81 KB
Newer Older
1 2 3
package dagcmd

import (
Łukasz Magiera's avatar
Łukasz Magiera committed
4
	"bytes"
5 6
	"fmt"
	"io"
7
	"math"
8 9 10
	"strings"

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

keks's avatar
keks committed
16
	logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log"
Steven Allen's avatar
Steven Allen committed
17 18
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
19 20
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
	files "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit/files"
21 22
)

keks's avatar
keks committed
23 24
var log = logging.Logger("cmds/files")

25
var DagCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
26
	Helptext: cmdkit.HelpText{
Jeromy's avatar
Jeromy committed
27 28 29 30 31 32 33
		Tagline: "Interact with ipld dag objects.",
		ShortDescription: `
'ipfs dag' is used for creating and manipulating dag objects.

This subcommand is currently an experimental feature, but it is intended
to deprecate and replace the existing 'ipfs object' command moving forward.
		`,
34 35
	},
	Subcommands: map[string]*cmds.Command{
Łukasz Magiera's avatar
Łukasz Magiera committed
36 37 38
		"put":     DagPutCmd,
		"get":     DagGetCmd,
		"resolve": DagResolveCmd,
39 40 41
	},
}

Łukasz Magiera's avatar
Łukasz Magiera committed
42
// OutputObject is the output type of 'dag put' command
43 44 45 46
type OutputObject struct {
	Cid *cid.Cid
}

Łukasz Magiera's avatar
Łukasz Magiera committed
47
// ResolveOutput is the output type of 'dag resolve' command
Łukasz Magiera's avatar
Łukasz Magiera committed
48 49 50 51 52
type ResolveOutput struct {
	Cid     *cid.Cid
	RemPath string
}

53
var DagPutCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
54
	Helptext: cmdkit.HelpText{
55
		Tagline: "Add a dag node to ipfs.",
Jeromy's avatar
Jeromy committed
56 57 58 59
		ShortDescription: `
'ipfs dag put' accepts input from a file or stdin and parses it
into an object of the specified format.
`,
60
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
61 62
	Arguments: []cmdkit.Argument{
		cmdkit.FileArg("object data", true, true, "The object to put").EnableStdin(),
63
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
64
	Options: []cmdkit.Option{
65 66
		cmdkit.StringOption("format", "f", "Format that the object will be added as.").WithDefault("cbor"),
		cmdkit.StringOption("input-enc", "Format that the input object will be.").WithDefault("json"),
67
		cmdkit.BoolOption("pin", "Pin this object when adding."),
68
		cmdkit.StringOption("hash", "Hash function to use").WithDefault(""),
69 70 71 72
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
73
			res.SetError(err, cmdkit.ErrNormal)
74 75 76 77 78
			return
		}

		ienc, _, _ := req.Option("input-enc").String()
		format, _, _ := req.Option("format").String()
79
		hash, _, err := req.Option("hash").String()
80 81
		dopin, _, err := req.Option("pin").Bool()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
82
			res.SetError(err, cmdkit.ErrNormal)
83 84
			return
		}
85

86 87 88 89 90 91 92 93
		// mhType tells inputParser which hash should be used. MaxUint64 means 'use
		// default hash' (sha256 for cbor, sha1 for git..)
		mhType := uint64(math.MaxUint64)

		if hash != "" {
			var ok bool
			mhType, ok = mh.Names[hash]
			if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
94 95
				res.SetError(fmt.Errorf("%s in not a valid multihash name", hash), cmdkit.ErrNormal)

96 97 98 99
				return
			}
		}

100 101 102 103
		outChan := make(chan interface{}, 8)
		res.SetOutput((<-chan interface{})(outChan))

		addAllAndPin := func(f files.File) error {
Łukasz Magiera's avatar
Łukasz Magiera committed
104 105 106
			cids := cid.NewSet()
			b := n.DAG.Batch()

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
			for {
				file, err := f.NextFile()
				if err == io.EOF {
					// Finished the list of files.
					break
				} else if err != nil {
					return err
				}

				nds, err := coredag.ParseInputs(ienc, format, file, mhType, -1)
				if err != nil {
					return err
				}
				if len(nds) == 0 {
					return fmt.Errorf("no node returned from ParseInputs")
				}

Łukasz Magiera's avatar
Łukasz Magiera committed
124 125 126 127
				for _, nd := range nds {
					_, err := b.Add(nd)
					if err != nil {
						return err
128
					}
Łukasz Magiera's avatar
Łukasz Magiera committed
129
				}
130

Łukasz Magiera's avatar
Łukasz Magiera committed
131 132 133 134
				cid := nds[0].Cid()
				cids.Add(cid)
				outChan <- &OutputObject{Cid: cid}
			}
135

Łukasz Magiera's avatar
Łukasz Magiera committed
136 137 138
			if err := b.Commit(); err != nil {
				return err
			}
139

Łukasz Magiera's avatar
Łukasz Magiera committed
140 141
			if dopin {
				defer n.Blockstore.PinLock().Unlock()
Łukasz Magiera's avatar
Łukasz Magiera committed
142

Łukasz Magiera's avatar
Łukasz Magiera committed
143 144
				cids.ForEach(func(c *cid.Cid) error {
					n.Pinning.PinWithMode(c, pin.Recursive)
Łukasz Magiera's avatar
Łukasz Magiera committed
145
					return nil
Łukasz Magiera's avatar
Łukasz Magiera committed
146
				})
Łukasz Magiera's avatar
Łukasz Magiera committed
147

Łukasz Magiera's avatar
Łukasz Magiera committed
148 149 150 151
				err := n.Pinning.Flush()
				if err != nil {
					return err
				}
Jeromy's avatar
Jeromy committed
152
			}
153

154
			return nil
155
		}
156

157 158 159
		go func() {
			defer close(outChan)
			if err := addAllAndPin(req.Files()); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
160
				res.SetError(err, cmdkit.ErrNormal)
161 162
				return
			}
163
		}()
164 165 166 167
	},
	Type: OutputObject{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
168 169 170
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
171 172
			}

Jan Winkelmann's avatar
Jan Winkelmann committed
173 174 175
			oobj, ok := v.(*OutputObject)
			if !ok {
				return nil, e.TypeErr(oobj, v)
176 177
			}

Jan Winkelmann's avatar
Jan Winkelmann committed
178
			return strings.NewReader(oobj.Cid.String() + "\n"), nil
179 180 181 182 183
		},
	},
}

var DagGetCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
184
	Helptext: cmdkit.HelpText{
185
		Tagline: "Get a dag node from ipfs.",
Jeromy's avatar
Jeromy committed
186
		ShortDescription: `
187 188
'ipfs dag get' fetches a dag node from ipfs and prints it out in the specifed
format.
Jeromy's avatar
Jeromy committed
189
`,
190
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
191 192
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The object to get").EnableStdin(),
193 194 195 196
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
197
			res.SetError(err, cmdkit.ErrNormal)
198 199 200
			return
		}

201
		p, err := path.ParsePath(req.Arguments()[0])
202
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
203
			res.SetError(err, cmdkit.ErrNormal)
204 205 206
			return
		}

207
		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
208
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
209
			res.SetError(err, cmdkit.ErrNormal)
210 211 212
			return
		}

213 214 215 216
		var out interface{} = obj
		if len(rem) > 0 {
			final, _, err := obj.Resolve(rem)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
217
				res.SetError(err, cmdkit.ErrNormal)
218 219 220 221 222 223
				return
			}
			out = final
		}

		res.SetOutput(out)
224 225
	},
}
Łukasz Magiera's avatar
Łukasz Magiera committed
226

Łukasz Magiera's avatar
Łukasz Magiera committed
227
// DagResolveCmd returns address of highest block within a path and a path remainder
Łukasz Magiera's avatar
Łukasz Magiera committed
228
var DagResolveCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
229
	Helptext: cmdkit.HelpText{
Łukasz Magiera's avatar
Łukasz Magiera committed
230 231 232 233 234
		Tagline: "Resolve ipld block",
		ShortDescription: `
'ipfs dag resolve' fetches a dag node from ipfs, prints it's address and remaining path.
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
235 236
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The path to resolve").EnableStdin(),
Łukasz Magiera's avatar
Łukasz Magiera committed
237 238 239 240
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
241
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
242 243 244 245 246
			return
		}

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
247
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
248 249 250 251 252
			return
		}

		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
253
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
254 255 256 257 258 259 260 261 262 263
			return
		}

		res.SetOutput(&ResolveOutput{
			Cid:     obj.Cid(),
			RemPath: path.Join(rem),
		})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
264 265 266 267 268 269
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			output := v.(*ResolveOutput)
Łukasz Magiera's avatar
Łukasz Magiera committed
270 271 272 273 274 275 276 277 278 279 280 281 282
			buf := new(bytes.Buffer)
			p := output.Cid.String()
			if output.RemPath != "" {
				p = path.Join([]string{p, output.RemPath})
			}

			buf.WriteString(p)

			return buf, nil
		},
	},
	Type: ResolveOutput{},
}
Jan Winkelmann's avatar
Jan Winkelmann committed
283 284 285 286 287 288 289 290 291 292 293 294 295 296

// copy+pasted from ../commands.go
func unwrapOutput(i interface{}) (interface{}, error) {
	var (
		ch <-chan interface{}
		ok bool
	)

	if ch, ok = i.(<-chan interface{}); !ok {
		return nil, e.TypeErr(ch, i)
	}

	return <-ch, nil
}