dag.go 7.01 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
	"strings"

10 11
	path "gx/ipfs/QmdrpbDgeYH3VxkCciQCJY5LkDYdXtig6unDzQmMxFtWEw/go-path"

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

17
	cid "gx/ipfs/QmPSQnBKM9g7BaUcZCvswUJVscQ1ipjmwxN5PXCjkp9EQ7/go-cid"
18
	mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
19
	files "gx/ipfs/QmZMWMvWMVKCbHetJ4RgndbuEF1io2UpUxwQwtNjtYPzSC/go-ipfs-files"
20
	ipld "gx/ipfs/QmdDXJs4axxefSPgK6Y1QhpJWKuDPnGJiqgq4uncb4rFHL/go-ipld-format"
21
	cmdkit "gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
22 23 24
)

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

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

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

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

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

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

95 96 97 98
				return
			}
		}

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

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

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

Łukasz Magiera's avatar
Łukasz Magiera committed
130 131
				cid := nds[0].Cid()
				cids.Add(cid)
Alec Brickner's avatar
Alec Brickner committed
132 133 134 135 136 137

				select {
				case outChan <- &OutputObject{Cid: cid}:
				case <-req.Context().Done():
					return nil
				}
Łukasz Magiera's avatar
Łukasz Magiera committed
138
			}
139

Łukasz Magiera's avatar
Łukasz Magiera committed
140 141 142
			if err := b.Commit(); err != nil {
				return err
			}
143

Łukasz Magiera's avatar
Łukasz Magiera committed
144 145
			if dopin {
				defer n.Blockstore.PinLock().Unlock()
Łukasz Magiera's avatar
Łukasz Magiera committed
146

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

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

158
			return nil
159
		}
160

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

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

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

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

205
		p, err := path.ParsePath(req.Arguments()[0])
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 215 216
		lastCid, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		obj, err := n.DAG.Get(req.Context(), lastCid)
217
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
218
			res.SetError(err, cmdkit.ErrNormal)
219 220 221
			return
		}

222 223 224 225
		var out interface{} = obj
		if len(rem) > 0 {
			final, _, err := obj.Resolve(rem)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
226
				res.SetError(err, cmdkit.ErrNormal)
227 228 229 230 231 232
				return
			}
			out = final
		}

		res.SetOutput(out)
233 234
	},
}
Łukasz Magiera's avatar
Łukasz Magiera committed
235

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

		p, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
256
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
257 258 259
			return
		}

260
		lastCid, rem, err := n.Resolver.ResolveToLastNode(req.Context(), p)
Łukasz Magiera's avatar
Łukasz Magiera committed
261
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
262
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
263 264 265 266
			return
		}

		res.SetOutput(&ResolveOutput{
267
			Cid:     lastCid,
Łukasz Magiera's avatar
Łukasz Magiera committed
268 269 270 271 272
			RemPath: path.Join(rem),
		})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
273 274 275 276 277 278
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			output := v.(*ResolveOutput)
Łukasz Magiera's avatar
Łukasz Magiera committed
279 280 281 282 283 284 285 286 287 288 289 290 291
			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
292 293 294 295 296 297 298 299 300 301 302 303 304 305

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