object.go 16.8 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
	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
Jan Winkelmann's avatar
Jan Winkelmann committed
18
	e "github.com/ipfs/go-ipfs/core/commands/e"
19 20
	dag "github.com/ipfs/go-ipfs/merkledag"
	path "github.com/ipfs/go-ipfs/path"
Łukasz Magiera's avatar
Łukasz Magiera committed
21
	pin "github.com/ipfs/go-ipfs/pin"
22
	ft "github.com/ipfs/go-ipfs/unixfs"
23

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

29 30
// 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")
31

kpcyrd's avatar
kpcyrd committed
32
const inputLimit = 2 << 20
33

34 35
type Node struct {
	Links []Link
36
	Data  string
37 38
}

39 40 41 42 43 44
type Link struct {
	Name, Hash string
	Size       uint64
}

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

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

	Subcommands: map[string]*cmds.Command{
58
		"data":  ObjectDataCmd,
Jeromy's avatar
Jeromy committed
59
		"diff":  ObjectDiffCmd,
60
		"get":   ObjectGetCmd,
Richard Littauer's avatar
Richard Littauer committed
61
		"links": ObjectLinksCmd,
62 63
		"new":   ObjectNewCmd,
		"patch": ObjectPatchCmd,
Richard Littauer's avatar
Richard Littauer committed
64 65
		"put":   ObjectPutCmd,
		"stat":  ObjectStatCmd,
66 67 68
	},
}

69
var ObjectDataCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
70
	Helptext: cmdkit.HelpText{
71
		Tagline: "Output the raw bytes of an IPFS object.",
72
		ShortDescription: `
73 74
'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.
75 76
`,
		LongDescription: `
77 78
'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.
79

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

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

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

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

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

		res.SetOutput(bytes.NewReader(pbnode.Data()))
114 115 116
	},
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

275
var ObjectStatCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
276
	Helptext: cmdkit.HelpText{
rht's avatar
rht committed
277
		Tagline: "Get stats for the DAG node named by <key>.",
278 279 280 281 282 283 284 285 286 287 288 289
		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
290 291
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format.").EnableStdin(),
292
	},
293
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
294
		n, err := req.InvocContext().GetNode()
295
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
296
			res.SetError(err, cmdkit.ErrNormal)
297
			return
298 299
		}

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

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

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

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

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

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

339
			return buf, nil
340 341 342 343
		},
	},
}

344
var ObjectPutCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
345
	Helptext: cmdkit.HelpText{
346
		Tagline: "Store input as a DAG object, print its key.",
347 348 349 350 351 352
		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.
353 354
It reads from stdin, and the output is a base58 encoded multihash.

Henry's avatar
Henry committed
355 356
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
357
	* "protobuf"
Henry's avatar
Henry committed
358
	* "json" (default)
Dylan Powers's avatar
Dylan Powers committed
359 360 361

Examples:

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

364 365
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
366 367 368 369 370 371 372 373 374 375

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

376
And then run:
Dylan Powers's avatar
Dylan Powers committed
377

378
	$ ipfs object put node.json
379
`,
380
	},
381

Jan Winkelmann's avatar
Jan Winkelmann committed
382 383
	Arguments: []cmdkit.Argument{
		cmdkit.FileArg("data", true, false, "Data to be stored as a DAG object.").EnableStdin(),
Henry's avatar
Henry committed
384
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
385
	Options: []cmdkit.Option{
386 387
		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"),
388
		cmdkit.BoolOption("pin", "Pin this object when adding."),
Łukasz Magiera's avatar
Łukasz Magiera committed
389
		cmdkit.BoolOption("quiet", "q", "Write minimal output."),
390
	},
391
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
392
		n, err := req.InvocContext().GetNode()
393
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
394
			res.SetError(err, cmdkit.ErrNormal)
395
			return
396
		}
397

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

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

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

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

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

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

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
451 452 453 454 455 456 457 458 459
			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
460 461 462 463 464 465
			out := obj.Hash + "\n"
			if !quiet {
				out = "added " + out
			}

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

471
var ObjectNewCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
472
	Helptext: cmdkit.HelpText{
473
		Tagline: "Create a new object from an ipfs template.",
474 475 476 477 478 479 480 481
		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.
482 483 484

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

497
		node := new(dag.ProtoNode)
498 499 500 501 502
		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
503
				res.SetError(err, cmdkit.ErrNormal)
504 505 506 507
				return
			}
		}

508
		err = n.DAG.Add(req.Context(), node)
509
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
510
			res.SetError(err, cmdkit.ErrNormal)
511 512
			return
		}
513
		res.SetOutput(&Object{Hash: node.Cid().String()})
514 515 516
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
517 518 519 520 521 522 523 524 525 526 527
			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
528 529 530 531 532
		},
	},
	Type: Object{},
}

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

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

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

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

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

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

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

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

	case objectEncodingProtobuf:
578
		dagnode, err = dag.DecodeProtobuf(data)
579

580 581 582 583 584 585 586 587 588 589 590 591 592
	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
		}

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

598 599 600 601 602 603 604 605
	default:
		return nil, ErrUnknownObjectEnc
	}

	if err != nil {
		return nil, err
	}

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

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

// 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"
622
	objectEncodingXML                     = "xml"
623 624 625 626 627 628 629 630 631 632 633 634
)

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

	return objectEncoding(v)
}

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

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

	return output, nil
}
652

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

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

	return dagnode, nil
}
685 686 687 688

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

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