object.go 16.5 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"
Steven Allen's avatar
Steven Allen committed
22
	cmdkit "gx/ipfs/QmUyfy4QSr3NXym4etEiRyxBLqqAeKHJuRdi8AACxg63fZ/go-ipfs-cmdkit"
23

24 25
	cid "gx/ipfs/QmNp85zy9RLrQ5oQD4hPyS39ezrrXpcaa7R4Y9kxdWQLLQ/go-cid"
	node "gx/ipfs/QmPN7cwmpcc4DWXb4KTB9dNAJgjuPY69h3npsMfhRrQL9c/go-ipld-format"
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 385 386
	Options: []cmdkit.Option{
		cmdkit.StringOption("inputenc", "Encoding type of input data. One of: {\"protobuf\", \"json\"}.").Default("json"),
		cmdkit.StringOption("datafieldenc", "Encoding type of the data field, either \"text\" or \"base64\".").Default("text"),
387
		cmdkit.BoolOption("pin", "Pin this object when adding."),
388
	},
389
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
390
		n, err := req.InvocContext().GetNode()
391
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
392
			res.SetError(err, cmdkit.ErrNormal)
393
			return
394
		}
395

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

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

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

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

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

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

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

443
		res.SetOutput(&Object{Hash: objectCid.String()})
444
	},
445
	Marshalers: cmds.MarshalerMap{
446
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
Jan Winkelmann's avatar
Jan Winkelmann committed
447 448 449 450 451 452 453 454 455 456
			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("added " + obj.Hash + "\n"), nil
457 458
		},
	},
459
	Type: Object{},
460 461
}

462
var ObjectNewCmd = &cmds.Command{
Jan Winkelmann's avatar
Jan Winkelmann committed
463
	Helptext: cmdkit.HelpText{
464
		Tagline: "Create a new object from an ipfs template.",
465 466 467 468 469 470 471 472
		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.
473 474 475

Available templates:
	* unixfs-dir
476 477
`,
	},
Jan Winkelmann's avatar
Jan Winkelmann committed
478 479
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("template", false, false, "Template to use. Optional."),
480 481
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
482
		n, err := req.InvocContext().GetNode()
483
		if err != nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
484
			res.SetError(err, cmdkit.ErrNormal)
485 486 487
			return
		}

488
		node := new(dag.ProtoNode)
489 490 491 492 493
		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
494
				res.SetError(err, cmdkit.ErrNormal)
495 496 497 498 499 500
				return
			}
		}

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

524
func nodeFromTemplate(template string) (*dag.ProtoNode, error) {
525 526
	switch template {
	case "unixfs-dir":
527
		return ft.EmptyDirNode(), nil
528 529 530 531 532
	default:
		return nil, fmt.Errorf("template '%s' not found", template)
	}
}

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

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

539
	data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
540 541 542 543 544 545 546 547
	if err != nil {
		return nil, err
	}

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

548
	var dagnode *dag.ProtoNode
549 550
	switch getObjectEnc(encoding) {
	case objectEncodingJSON:
551 552 553 554 555 556
		node := new(Node)
		err = json.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}

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

563
		dagnode, err = deserializeNode(node, dataFieldEncoding)
564 565 566
		if err != nil {
			return nil, err
		}
567 568

	case objectEncodingProtobuf:
569
		dagnode, err = dag.DecodeProtobuf(data)
570

571 572 573 574 575 576 577 578 579 580 581 582 583
	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
		}

584
		dagnode, err = deserializeNode(node, dataFieldEncoding)
585 586 587 588
		if err != nil {
			return nil, err
		}

589 590 591 592 593 594 595 596
	default:
		return nil, ErrUnknownObjectEnc
	}

	if err != nil {
		return nil, err
	}

597
	_, err = n.DAG.Add(dagnode)
598 599 600 601
	if err != nil {
		return nil, err
	}

602
	return dagnode.Cid(), nil
603 604 605 606 607 608 609 610 611 612
}

// 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"
613
	objectEncodingXML                     = "xml"
614 615 616 617 618 619 620 621 622 623 624 625
)

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
626
func getOutput(dagnode node.Node) (*Object, error) {
Jeromy's avatar
Jeromy committed
627
	c := dagnode.Cid()
628
	output := &Object{
Jeromy's avatar
Jeromy committed
629
		Hash:  c.String(),
630
		Links: make([]Link, len(dagnode.Links())),
631 632
	}

633
	for i, link := range dagnode.Links() {
634 635
		output.Links[i] = Link{
			Name: link.Name,
636
			Hash: link.Cid.String(),
637 638 639 640 641 642
			Size: link.Size,
		}
	}

	return output, nil
}
643

644
// converts the Node object into a real dag.ProtoNode
Jeromy's avatar
Jeromy committed
645
func deserializeNode(nd *Node, dataFieldEncoding string) (*dag.ProtoNode, error) {
646
	dagnode := new(dag.ProtoNode)
slothbag's avatar
slothbag committed
647 648
	switch dataFieldEncoding {
	case "text":
Jeromy's avatar
Jeromy committed
649
		dagnode.SetData([]byte(nd.Data))
slothbag's avatar
slothbag committed
650
	case "base64":
Jeromy's avatar
Jeromy committed
651
		data, _ := base64.StdEncoding.DecodeString(nd.Data)
652
		dagnode.SetData(data)
slothbag's avatar
slothbag committed
653 654
	default:
		return nil, fmt.Errorf("Unkown data field encoding")
655 656
	}

Jeromy's avatar
Jeromy committed
657 658
	dagnode.SetLinks(make([]*node.Link, len(nd.Links)))
	for i, link := range nd.Links {
659
		c, err := cid.Decode(link.Hash)
660 661 662
		if err != nil {
			return nil, err
		}
Jeromy's avatar
Jeromy committed
663
		dagnode.Links()[i] = &node.Link{
664 665
			Name: link.Name,
			Size: link.Size,
666
			Cid:  c,
667 668 669 670 671
		}
	}

	return dagnode, nil
}
672 673 674 675

func NodeEmpty(node *Node) bool {
	return (node.Data == "" && len(node.Links) == 0)
}
Jan Winkelmann's avatar
Jan Winkelmann committed
676 677 678 679 680 681 682 683 684 685 686 687 688 689

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