dag.go 6.7 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

16 17
	cmdkit "gx/ipfs/QmVD1W3MC8Hk1WZgFQPWWmBECJ3X72BgUYf9eCQ4PGzPps/go-ipfs-cmdkit"
	files "gx/ipfs/QmVD1W3MC8Hk1WZgFQPWWmBECJ3X72BgUYf9eCQ4PGzPps/go-ipfs-cmdkit/files"
Steven Allen's avatar
Steven Allen committed
18 19
	mh "gx/ipfs/QmYeKnKpubCMRiq3PGZcTREErthbb5Q9cXsCoSkD9bjEBd/go-multihash"
	cid "gx/ipfs/QmeSrf6pzut73u6zLQkRFQ3ygt3k6XFT2kjdYP8Tnkwwyg/go-cid"
20 21 22
)

var DagCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
23
	Helptext: cmdkit.HelpText{
Jeromy's avatar
Jeromy committed
24 25 26 27 28 29 30
		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.
		`,
31 32
	},
	Subcommands: map[string]*cmds.Command{
Łukasz Magiera's avatar
Łukasz Magiera committed
33 34 35
		"put":     DagPutCmd,
		"get":     DagGetCmd,
		"resolve": DagResolveCmd,
36 37 38
	},
}

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

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

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

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

83 84 85 86 87 88 89 90
		// 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
91 92
				res.SetError(fmt.Errorf("%s in not a valid multihash name", hash), cmdkit.ErrNormal)

93 94 95 96
				return
			}
		}

97 98 99 100
		outChan := make(chan interface{}, 8)
		res.SetOutput((<-chan interface{})(outChan))

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

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
			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
121 122 123 124
				for _, nd := range nds {
					_, err := b.Add(nd)
					if err != nil {
						return err
125
					}
Łukasz Magiera's avatar
Łukasz Magiera committed
126
				}
127

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

Łukasz Magiera's avatar
Łukasz Magiera committed
133 134 135
			if err := b.Commit(); err != nil {
				return err
			}
136

Łukasz Magiera's avatar
Łukasz Magiera committed
137 138
			if dopin {
				defer n.Blockstore.PinLock().Unlock()
Łukasz Magiera's avatar
Łukasz Magiera committed
139

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

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

151
			return nil
152
		}
153

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

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

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

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

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

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

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

		res.SetOutput(out)
221 222
	},
}
Łukasz Magiera's avatar
Łukasz Magiera committed
223

Łukasz Magiera's avatar
Łukasz Magiera committed
224
// DagResolveCmd returns address of highest block within a path and a path remainder
Łukasz Magiera's avatar
Łukasz Magiera committed
225
var DagResolveCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
226
	Helptext: cmdkit.HelpText{
Łukasz Magiera's avatar
Łukasz Magiera committed
227 228 229 230 231
		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
232 233
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("ref", true, false, "The path to resolve").EnableStdin(),
Łukasz Magiera's avatar
Łukasz Magiera committed
234 235 236 237
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
238
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
239 240 241 242 243
			return
		}

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

		obj, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
250
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
251 252 253 254 255 256 257 258 259 260
			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
261 262 263 264 265 266
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			output := v.(*ResolveOutput)
Łukasz Magiera's avatar
Łukasz Magiera committed
267 268 269 270 271 272 273 274 275 276 277 278 279
			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
280 281 282 283 284 285 286 287 288 289 290 291 292 293

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