dag.go 6.9 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
	node "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
22 23
)

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

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

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

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

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

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

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

97 98 99 100
				return
			}
		}

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

		addAllAndPin := func(f files.File) error {
Łukasz Magiera's avatar
Łukasz Magiera committed
105
			cids := cid.NewSet()
106
			b := node.NewBatch(req.Context(), n.DAG)
Łukasz Magiera's avatar
Łukasz Magiera committed
107

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

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

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

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

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

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

155
			return nil
156
		}
157

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

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

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

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

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

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

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

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

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

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

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

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

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