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

Steven Allen's avatar
Steven Allen committed
16 17
	mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
18 19
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
	files "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit/files"
20
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
21 22 23
)

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

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

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

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

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

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

94 95 96 97
				return
			}
		}

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

		addAllAndPin := func(f files.File) error {
Łukasz Magiera's avatar
Łukasz Magiera committed
102
			cids := cid.NewSet()
103
			b := ipld.NewBatch(req.Context(), n.DAG)
Łukasz Magiera's avatar
Łukasz Magiera committed
104

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

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

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

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

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

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

152
			return nil
153
		}
154

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

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

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

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

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

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

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

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

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

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

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

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

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