object.go 17.1 KB
Newer Older
1
package objectcmd
2 3 4

import (
	"bytes"
5
	"context"
6
	"encoding/base64"
7
	"encoding/json"
8
	"encoding/xml"
9
	"errors"
10
	"fmt"
11 12
	"io"
	"io/ioutil"
13
	"strings"
14
	"text/tabwriter"
15

16 17
	oldcmds "github.com/ipfs/go-ipfs/commands"
	lgc "github.com/ipfs/go-ipfs/commands/legacy"
18
	core "github.com/ipfs/go-ipfs/core"
Jan Winkelmann's avatar
Jan Winkelmann committed
19
	e "github.com/ipfs/go-ipfs/core/commands/e"
20 21
	dag "github.com/ipfs/go-ipfs/merkledag"
	path "github.com/ipfs/go-ipfs/path"
Łukasz Magiera's avatar
Łukasz Magiera committed
22
	pin "github.com/ipfs/go-ipfs/pin"
23
	ft "github.com/ipfs/go-ipfs/unixfs"
24

Steven Allen's avatar
Steven Allen committed
25
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
26
	cmdkit "gx/ipfs/QmceUdzxkimdYsgtX733uNgzf1DLHyBKN6ehGSp85ayppM/go-ipfs-cmdkit"
27
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
28
	cmds "gx/ipfs/QmfAkMSt9Fwzk48QDJecPcwCUjnf2uG7MLnmCGTp4C6ouL/go-ipfs-cmds"
29 30
)

31 32
// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 2m
var ErrObjectTooLarge = errors.New("input object was too large. limit is 2mbytes")
33

kpcyrd's avatar
kpcyrd committed
34
const inputLimit = 2 << 20
35

36 37
type Node struct {
	Links []Link
38
	Data  string
39 40
}

41 42 43 44 45 46
type Link struct {
	Name, Hash string
	Size       uint64
}

type Object struct {
Jeromy's avatar
Jeromy committed
47 48
	Hash  string `json:"Hash,omitempty"`
	Links []Link `json:"Links,omitempty"`
49 50
}

51
var ObjectCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
52
	Helptext: cmdkit.HelpText{
53
		Tagline: "Interact with IPFS objects.",
Matt Bell's avatar
Matt Bell committed
54 55 56
		ShortDescription: `
'ipfs object' is a plumbing command used to manipulate DAG objects
directly.`,
57
	},
58 59

	Subcommands: map[string]*cmds.Command{
60 61 62 63 64
		"data":  lgc.NewCommand(ObjectDataCmd),
		"diff":  lgc.NewCommand(ObjectDiffCmd),
		"get":   lgc.NewCommand(ObjectGetCmd),
		"links": lgc.NewCommand(ObjectLinksCmd),
		"new":   lgc.NewCommand(ObjectNewCmd),
65
		"patch": ObjectPatchCmd,
66 67
		"put":   lgc.NewCommand(ObjectPutCmd),
		"stat":  lgc.NewCommand(ObjectStatCmd),
68 69 70
	},
}

71
var ObjectDataCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
72
	Helptext: cmdkit.HelpText{
73
		Tagline: "Output the raw bytes of an IPFS object.",
74
		ShortDescription: `
75 76
'ipfs object data' is a plumbing command for retrieving the raw bytes stored
in a DAG node. It outputs to stdout, and <key> is a base58 encoded multihash.
77 78
`,
		LongDescription: `
79 80
'ipfs object data' is a plumbing command for retrieving the raw bytes stored
in a DAG node. It outputs to stdout, and <key> is a base58 encoded multihash.
81

82 83
Note that the "--encoding" option does not affect the output, since the output
is the raw data of the object.
84
`,
85
	},
86

Jan Winkelmann's avatar
Jan Winkelmann committed
87 88
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(),
89
	},
90
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
91
		n, err := req.InvocContext().GetNode()
92
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
93
			res.SetError(err, cmdkit.ErrNormal)
94
			return
95
		}
96

Jeromy's avatar
Jeromy committed
97 98
		fpath, err := path.ParsePath(req.Arguments()[0])
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
99
			res.SetError(err, cmdkit.ErrNormal)
Jeromy's avatar
Jeromy committed
100 101 102
			return
		}

103
		node, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath)
104
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
105
			res.SetError(err, cmdkit.ErrNormal)
106 107
			return
		}
108 109 110

		pbnode, ok := node.(*dag.ProtoNode)
		if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
111
			res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal)
112 113 114 115
			return
		}

		res.SetOutput(bytes.NewReader(pbnode.Data()))
116 117 118
	},
}

119
var ObjectLinksCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
120
	Helptext: cmdkit.HelpText{
121
		Tagline: "Output the links pointed to by the specified object.",
122
		ShortDescription: `
rht's avatar
rht committed
123
'ipfs object links' is a plumbing command for retrieving the links from
Matt Bell's avatar
Matt Bell committed
124 125
a DAG node. It outputs to stdout, and <key> is a base58 encoded
multihash.
126 127
`,
	},
128

Jan Winkelmann's avatar
Jan Winkelmann committed
129 130
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(),
131
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
132
	Options: []cmdkit.Option{
133
		cmdkit.BoolOption("headers", "v", "Print table headers (Hash, Size, Name)."),
palkeo's avatar
palkeo committed
134
	},
135
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
136
		n, err := req.InvocContext().GetNode()
137
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
138
			res.SetError(err, cmdkit.ErrNormal)
139
			return
140
		}
141

142 143
		// get options early -> exit early in case of error
		if _, _, err := req.Option("headers").Bool(); err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
144
			res.SetError(err, cmdkit.ErrNormal)
145 146 147
			return
		}

Jeromy's avatar
Jeromy committed
148
		fpath := path.Path(req.Arguments()[0])
149
		node, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath)
150
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
151
			res.SetError(err, cmdkit.ErrNormal)
152 153
			return
		}
154

155
		output, err := getOutput(node)
156
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
157
			res.SetError(err, cmdkit.ErrNormal)
158 159 160
			return
		}
		res.SetOutput(output)
161
	},
162 163
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
164 165 166 167 168 169 170 171 172 173
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			object, ok := v.(*Object)
			if !ok {
				return nil, e.TypeErr(object, v)
			}

174 175
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
176 177 178 179
			headers, _, _ := res.Request().Option("headers").Bool()
			if headers {
				fmt.Fprintln(w, "Hash\tSize\tName\t")
			}
180 181 182
			for _, link := range object.Links {
				fmt.Fprintf(w, "%s\t%v\t%s\t\n", link.Hash, link.Size, link.Name)
			}
183
			w.Flush()
184
			return buf, nil
185 186
		},
	},
187
	Type: Object{},
188 189
}

190
var ObjectGetCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
191
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
192
		Tagline: "Get and serialize the DAG node named by <key>.",
193
		ShortDescription: `
rht's avatar
rht committed
194
'ipfs object get' is a plumbing command for retrieving DAG nodes.
195 196
It serializes the DAG node to the format specified by the "--encoding"
flag. It outputs to stdout, and <key> is a base58 encoded multihash.
197 198
`,
		LongDescription: `
rht's avatar
rht committed
199
'ipfs object get' is a plumbing command for retrieving DAG nodes.
200 201
It serializes the DAG node to the format specified by the "--encoding"
flag. It outputs to stdout, and <key> is a base58 encoded multihash.
202

203 204 205 206
This command outputs data in the following encodings:
  * "protobuf"
  * "json"
  * "xml"
207
(Specified by the "--encoding" or "--enc" flag)`,
208
	},
209

Jan Winkelmann's avatar
Jan Winkelmann committed
210 211
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(),
212
	},
213
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
214
		n, err := req.InvocContext().GetNode()
215
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
216
			res.SetError(err, cmdkit.ErrNormal)
217
			return
218
		}
219

Jeromy's avatar
Jeromy committed
220
		fpath := path.Path(req.Arguments()[0])
221

222
		object, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath)
223
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
224
			res.SetError(err, cmdkit.ErrNormal)
225
			return
226 227
		}

228 229
		pbo, ok := object.(*dag.ProtoNode)
		if !ok {
Jan Winkelmann's avatar
Jan Winkelmann committed
230
			res.SetError(dag.ErrNotProtobuf, cmdkit.ErrNormal)
231 232 233
			return
		}

234
		node := &Node{
235 236
			Links: make([]Link, len(object.Links())),
			Data:  string(pbo.Data()),
237 238
		}

239
		for i, link := range object.Links() {
240
			node.Links[i] = Link{
241
				Hash: link.Cid.String(),
242 243 244 245 246
				Name: link.Name,
				Size: link.Size,
			}
		}

247
		res.SetOutput(node)
248
	},
249
	Type: Node{},
250 251
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Protobuf: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
252 253 254 255 256 257 258 259 260 261
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			node, ok := v.(*Node)
			if !ok {
				return nil, e.TypeErr(node, v)
			}

262 263
			// deserialize the Data field as text as this was the standard behaviour
			object, err := deserializeNode(node, "text")
264 265
			if err != nil {
				return nil, err
266
			}
267 268 269 270 271 272

			marshaled, err := object.Marshal()
			if err != nil {
				return nil, err
			}
			return bytes.NewReader(marshaled), nil
273 274 275 276
		},
	},
}

277
var ObjectStatCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
278
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
279
		Tagline: "Get stats for the DAG node named by <key>.",
280 281 282 283 284 285 286 287 288 289 290 291
		ShortDescription: `
'ipfs object stat' is a plumbing command to print DAG node statistics.
<key> is a base58 encoded multihash. It outputs to stdout:

	NumLinks        int number of links in link table
	BlockSize       int size of the raw, encoded data
	LinksSize       int size of the links segment
	DataSize        int size of the data segment
	CumulativeSize  int cumulative size of object and its references
`,
	},

Jan Winkelmann's avatar
Jan Winkelmann committed
292 293
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(),
294
	},
295
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
296
		n, err := req.InvocContext().GetNode()
297
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
298
			res.SetError(err, cmdkit.ErrNormal)
299
			return
300 301
		}

Jeromy's avatar
Jeromy committed
302
		fpath := path.Path(req.Arguments()[0])
303

304
		object, err := core.Resolve(req.Context(), n.Namesys, n.Resolver, fpath)
305
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
306
			res.SetError(err, cmdkit.ErrNormal)
307
			return
308 309 310 311
		}

		ns, err := object.Stat()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
312
			res.SetError(err, cmdkit.ErrNormal)
313
			return
314 315
		}

316
		res.SetOutput(ns)
317
	},
318
	Type: ipld.NodeStat{},
319 320
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
321 322 323 324 325
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

326
			ns, ok := v.(*ipld.NodeStat)
Jan Winkelmann's avatar
Jan Winkelmann committed
327 328 329
			if !ok {
				return nil, e.TypeErr(ns, v)
			}
330

331
			buf := new(bytes.Buffer)
332
			w := func(s string, n int) {
333
				fmt.Fprintf(buf, "%s: %d\n", s, n)
334 335 336 337 338 339 340
			}
			w("NumLinks", ns.NumLinks)
			w("BlockSize", ns.BlockSize)
			w("LinksSize", ns.LinksSize)
			w("DataSize", ns.DataSize)
			w("CumulativeSize", ns.CumulativeSize)

341
			return buf, nil
342 343 344 345
		},
	},
}

346
var ObjectPutCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
347
	Helptext: cmdkit.HelpText{
348
		Tagline: "Store input as a DAG object, print its key.",
349 350 351 352 353 354
		ShortDescription: `
'ipfs object put' is a plumbing command for storing DAG nodes.
It reads from stdin, and the output is a base58 encoded multihash.
`,
		LongDescription: `
'ipfs object put' is a plumbing command for storing DAG nodes.
355 356
It reads from stdin, and the output is a base58 encoded multihash.

Henry's avatar
Henry committed
357 358
Data should be in the format specified by the --inputenc flag.
--inputenc may be one of the following:
Matt Bell's avatar
Matt Bell committed
359
	* "protobuf"
Henry's avatar
Henry committed
360
	* "json" (default)
Dylan Powers's avatar
Dylan Powers committed
361 362 363

Examples:

364
	$ echo '{ "Data": "abc" }' | ipfs object put
Dylan Powers's avatar
Dylan Powers committed
365

366 367
This creates a node with the data 'abc' and no links. For an object with
links, create a file named 'node.json' with the contents:
Dylan Powers's avatar
Dylan Powers committed
368 369 370 371 372 373 374 375 376 377

    {
        "Data": "another",
        "Links": [ {
            "Name": "some link",
            "Hash": "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V",
            "Size": 8
        } ]
    }

378
And then run:
Dylan Powers's avatar
Dylan Powers committed
379

380
	$ ipfs object put node.json
381
`,
382
	},
383

Jan Winkelmann's avatar
Jan Winkelmann committed
384 385
	Arguments: []cmdkit.Argument{
		cmdkit.FileArg("data", true, false, "Data to be stored as a DAG object.").EnableStdin(),
Henry's avatar
Henry committed
386
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
387
	Options: []cmdkit.Option{
388 389
		cmdkit.StringOption("inputenc", "Encoding type of input data. One of: {\"protobuf\", \"json\"}.").WithDefault("json"),
		cmdkit.StringOption("datafieldenc", "Encoding type of the data field, either \"text\" or \"base64\".").WithDefault("text"),
390
		cmdkit.BoolOption("pin", "Pin this object when adding."),
Łukasz Magiera's avatar
Łukasz Magiera committed
391
		cmdkit.BoolOption("quiet", "q", "Write minimal output."),
392
	},
393
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
394
		n, err := req.InvocContext().GetNode()
395
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
396
			res.SetError(err, cmdkit.ErrNormal)
397
			return
398
		}
399

400 401
		input, err := req.Files().NextFile()
		if err != nil && err != io.EOF {
Jan Winkelmann's avatar
Jan Winkelmann committed
402
			res.SetError(err, cmdkit.ErrNormal)
403
			return
404 405
		}

406
		inputenc, _, err := req.Option("inputenc").String()
Henry's avatar
Henry committed
407
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
408
			res.SetError(err, cmdkit.ErrNormal)
Henry's avatar
Henry committed
409 410
			return
		}
411

412
		datafieldenc, _, err := req.Option("datafieldenc").String()
413
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
414
			res.SetError(err, cmdkit.ErrNormal)
415 416
			return
		}
slothbag's avatar
slothbag committed
417

Łukasz Magiera's avatar
Łukasz Magiera committed
418 419
		dopin, _, err := req.Option("pin").Bool()
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
420
			res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
421 422 423 424 425 426 427
			return
		}

		if dopin {
			defer n.Blockstore.PinLock().Unlock()
		}

428
		objectCid, err := objectPut(req.Context(), n, input, inputenc, datafieldenc)
429
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
430
			errType := cmdkit.ErrNormal
431
			if err == ErrUnknownObjectEnc {
Jan Winkelmann's avatar
Jan Winkelmann committed
432
				errType = cmdkit.ErrClient
433
			}
434 435
			res.SetError(err, errType)
			return
436 437
		}

Łukasz Magiera's avatar
Łukasz Magiera committed
438
		if dopin {
439
			n.Pinning.PinWithMode(objectCid, pin.Recursive)
Łukasz Magiera's avatar
Łukasz Magiera committed
440 441
			err = n.Pinning.Flush()
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
442
				res.SetError(err, cmdkit.ErrNormal)
Łukasz Magiera's avatar
Łukasz Magiera committed
443 444 445 446
				return
			}
		}

447
		res.SetOutput(&Object{Hash: objectCid.String()})
448
	},
449 450
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Łukasz Magiera's avatar
Łukasz Magiera committed
451 452
			quiet, _, _ := res.Request().Option("quiet").Bool()

Jan Winkelmann's avatar
Jan Winkelmann committed
453 454 455 456 457 458 459 460 461
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}
			obj, ok := v.(*Object)
			if !ok {
				return nil, e.TypeErr(obj, v)
			}

Łukasz Magiera's avatar
Łukasz Magiera committed
462 463 464 465 466 467
			out := obj.Hash + "\n"
			if !quiet {
				out = "added " + out
			}

			return strings.NewReader(out), nil
468 469
		},
	},
470
	Type: Object{},
471 472
}

473
var ObjectNewCmd = &oldcmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
474
	Helptext: cmdkit.HelpText{
475
		Tagline: "Create a new object from an ipfs template.",
476 477 478 479 480 481 482 483
		ShortDescription: `
'ipfs object new' is a plumbing command for creating new DAG nodes.
`,
		LongDescription: `
'ipfs object new' is a plumbing command for creating new DAG nodes.
By default it creates and returns a new empty merkledag node, but
you may pass an optional template argument to create a preformatted
node.
484 485 486

Available templates:
	* unixfs-dir
487 488
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
489 490
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("template", false, false, "Template to use. Optional."),
491
	},
492
	Run: func(req oldcmds.Request, res oldcmds.Response) {
Jeromy's avatar
Jeromy committed
493
		n, err := req.InvocContext().GetNode()
494
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
495
			res.SetError(err, cmdkit.ErrNormal)
496 497 498
			return
		}

499
		node := new(dag.ProtoNode)
500 501 502 503 504
		if len(req.Arguments()) == 1 {
			template := req.Arguments()[0]
			var err error
			node, err = nodeFromTemplate(template)
			if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
505
				res.SetError(err, cmdkit.ErrNormal)
506 507 508 509
				return
			}
		}

510
		err = n.DAG.Add(req.Context(), node)
511
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
512
			res.SetError(err, cmdkit.ErrNormal)
513 514
			return
		}
515
		res.SetOutput(&Object{Hash: node.Cid().String()})
516
	},
517 518
	Marshalers: oldcmds.MarshalerMap{
		oldcmds.Text: func(res oldcmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
519 520 521 522 523 524 525 526 527 528 529
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			obj, ok := v.(*Object)
			if !ok {
				return nil, e.TypeErr(obj, v)
			}

			return strings.NewReader(obj.Hash + "\n"), nil
530 531 532 533 534
		},
	},
	Type: Object{},
}

535
func nodeFromTemplate(template string) (*dag.ProtoNode, error) {
536 537
	switch template {
	case "unixfs-dir":
538
		return ft.EmptyDirNode(), nil
539 540 541 542 543
	default:
		return nil, fmt.Errorf("template '%s' not found", template)
	}
}

544 545 546
// ErrEmptyNode is returned when the input to 'ipfs object put' contains no data
var ErrEmptyNode = errors.New("no data or links in this node")

547
// objectPut takes a format option, serializes bytes from stdin and updates the dag with that data
548
func objectPut(ctx context.Context, n *core.IpfsNode, input io.Reader, encoding string, dataFieldEncoding string) (*cid.Cid, error) {
549

550
	data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
551 552 553 554 555 556 557 558
	if err != nil {
		return nil, err
	}

	if len(data) >= inputLimit {
		return nil, ErrObjectTooLarge
	}

559
	var dagnode *dag.ProtoNode
560 561
	switch getObjectEnc(encoding) {
	case objectEncodingJSON:
562 563 564 565 566 567
		node := new(Node)
		err = json.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}

568 569
		// check that we have data in the Node to add
		// otherwise we will add the empty object without raising an error
570
		if NodeEmpty(node) {
571 572 573
			return nil, ErrEmptyNode
		}

574
		dagnode, err = deserializeNode(node, dataFieldEncoding)
575 576 577
		if err != nil {
			return nil, err
		}
578 579

	case objectEncodingProtobuf:
580
		dagnode, err = dag.DecodeProtobuf(data)
581

582 583 584 585 586 587 588 589 590 591 592 593 594
	case objectEncodingXML:
		node := new(Node)
		err = xml.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}

		// check that we have data in the Node to add
		// otherwise we will add the empty object without raising an error
		if NodeEmpty(node) {
			return nil, ErrEmptyNode
		}

595
		dagnode, err = deserializeNode(node, dataFieldEncoding)
596 597 598 599
		if err != nil {
			return nil, err
		}

600 601 602 603 604 605 606 607
	default:
		return nil, ErrUnknownObjectEnc
	}

	if err != nil {
		return nil, err
	}

608
	err = n.DAG.Add(ctx, dagnode)
609 610 611 612
	if err != nil {
		return nil, err
	}

613
	return dagnode.Cid(), nil
614 615 616 617 618 619 620 621 622 623
}

// ErrUnknownObjectEnc is returned if a invalid encoding is supplied
var ErrUnknownObjectEnc = errors.New("unknown object encoding")

type objectEncoding string

const (
	objectEncodingJSON     objectEncoding = "json"
	objectEncodingProtobuf                = "protobuf"
624
	objectEncodingXML                     = "xml"
625 626 627 628 629 630 631 632 633 634 635 636
)

func getObjectEnc(o interface{}) objectEncoding {
	v, ok := o.(string)
	if !ok {
		// chosen as default because it's human readable
		return objectEncodingJSON
	}

	return objectEncoding(v)
}

637
func getOutput(dagnode ipld.Node) (*Object, error) {
Jeromy's avatar
Jeromy committed
638
	c := dagnode.Cid()
639
	output := &Object{
Jeromy's avatar
Jeromy committed
640
		Hash:  c.String(),
641
		Links: make([]Link, len(dagnode.Links())),
642 643
	}

644
	for i, link := range dagnode.Links() {
645 646
		output.Links[i] = Link{
			Name: link.Name,
647
			Hash: link.Cid.String(),
648 649 650 651 652 653
			Size: link.Size,
		}
	}

	return output, nil
}
654

655
// converts the Node object into a real dag.ProtoNode
Jeromy's avatar
Jeromy committed
656
func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) {
657
	dagnode := new(dag.ProtoNode)
slothbag's avatar
slothbag committed
658 659
	switch dataFieldEncoding {
	case "text":
Jeromy's avatar
Jeromy committed
660
		dagnode.SetData([]byte(nd.Data))
slothbag's avatar
slothbag committed
661
	case "base64":
Steven Allen's avatar
Steven Allen committed
662 663 664 665
		data, err := base64.StdEncoding.DecodeString(nd.Data)
		if err != nil {
			return nil, err
		}
666
		dagnode.SetData(data)
slothbag's avatar
slothbag committed
667
	default:
Łukasz Magiera's avatar
Łukasz Magiera committed
668
		return nil, fmt.Errorf("unkown data field encoding")
669 670
	}

Steven Allen's avatar
Steven Allen committed
671
	links := make([]*ipld.Link, len(nd.Links))
Jeromy's avatar
Jeromy committed
672
	for i, link := range nd.Links {
673
		c, err := cid.Decode(link.Hash)
674 675 676
		if err != nil {
			return nil, err
		}
Steven Allen's avatar
Steven Allen committed
677
		links[i] = &ipld.Link{
678 679
			Name: link.Name,
			Size: link.Size,
680
			Cid:  c,
681 682
		}
	}
Steven Allen's avatar
Steven Allen committed
683
	dagnode.SetLinks(links)
684 685 686

	return dagnode, nil
}
687 688

func NodeEmpty(node *Node) bool {
Łukasz Magiera's avatar
Łukasz Magiera committed
689
	return node.Data == "" && len(node.Links) == 0
690
}
Jan Winkelmann's avatar
Jan Winkelmann committed
691 692 693 694 695 696 697 698 699 700 701 702 703 704

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