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

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

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

Steven Allen's avatar
Steven Allen committed
23
	node "gx/ipfs/QmNwUEK7QbwSqyKBu3mMtToo8SUc6wQJ7gdZq4gGGJqfnf/go-ipld-format"
24
	cmdkit "gx/ipfs/QmVD1W3MC8Hk1WZgFQPWWmBECJ3X72BgUYf9eCQ4PGzPps/go-ipfs-cmdkit"
Steven Allen's avatar
Steven Allen committed
25
	cid "gx/ipfs/QmeSrf6pzut73u6zLQkRFQ3ygt3k6XFT2kjdYP8Tnkwwyg/go-cid"
26 27
)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Examples:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	if err != nil {
		return nil, err
	}

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

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

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

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

	return objectEncoding(v)
}

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

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

	return output, nil
}
651

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

Jeromy's avatar
Jeromy committed
665 666
	dagnode.SetLinks(make([]*node.Link, len(nd.Links)))
	for i, link := range nd.Links {
667
		c, err := cid.Decode(link.Hash)
668 669 670
		if err != nil {
			return nil, err
		}
Jeromy's avatar
Jeromy committed
671
		dagnode.Links()[i] = &node.Link{
672 673
			Name: link.Name,
			Size: link.Size,
674
			Cid:  c,
675 676 677 678 679
		}
	}

	return dagnode, nil
}
680 681 682 683

func NodeEmpty(node *Node) bool {
	return (node.Data == "" && len(node.Links) == 0)
}
Jan Winkelmann's avatar
Jan Winkelmann committed
684 685 686 687 688 689 690 691 692 693 694 695 696 697

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