object.go 19.4 KB
Newer Older
1 2 3 4 5
package commands

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

14
	mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
15

16
	key "github.com/ipfs/go-ipfs/blocks/key"
17 18 19
	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
	dag "github.com/ipfs/go-ipfs/merkledag"
20
	dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
21
	path "github.com/ipfs/go-ipfs/path"
22 23
	ft "github.com/ipfs/go-ipfs/unixfs"
	u "github.com/ipfs/go-ipfs/util"
24 25 26 27 28 29 30
)

// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
var ErrObjectTooLarge = errors.New("input object was too large. limit is 512kbytes")

const inputLimit = 512 * 1024

31 32
type Node struct {
	Links []Link
33
	Data  string
34 35
}

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

type Object struct {
	Hash  string
	Links []Link
}

46
var ObjectCmd = &cmds.Command{
47
	Helptext: cmds.HelpText{
Matt Bell's avatar
Matt Bell committed
48 49 50 51 52
		Tagline: "Interact with ipfs objects",
		ShortDescription: `
'ipfs object' is a plumbing command used to manipulate DAG objects
directly.`,
		Synopsis: `
53 54 55 56 57 58 59
ipfs object get <key>       - Get the DAG node named by <key>
ipfs object put <data>      - Stores input, outputs its key
ipfs object data <key>      - Outputs raw bytes in an object
ipfs object links <key>     - Outputs links pointed to by object
ipfs object stat <key>      - Outputs statistics of object
ipfs object new <template>  - Create new ipfs objects
ipfs object patch <args>    - Create new object from old ones
Matt Bell's avatar
Matt Bell committed
60
`,
61
	},
62 63 64 65 66 67

	Subcommands: map[string]*cmds.Command{
		"data":  objectDataCmd,
		"links": objectLinksCmd,
		"get":   objectGetCmd,
		"put":   objectPutCmd,
68
		"stat":  objectStatCmd,
69 70
		"new":   objectNewCmd,
		"patch": objectPatchCmd,
71 72 73 74
	},
}

var objectDataCmd = &cmds.Command{
75 76 77
	Helptext: cmds.HelpText{
		Tagline: "Outputs the raw bytes in an IPFS object",
		ShortDescription: `
78
ipfs object data is a plumbing command for retreiving the raw bytes stored in
79 80 81 82
a DAG node. It outputs to stdout, and <key> is a base58 encoded
multihash.
`,
		LongDescription: `
83
ipfs object data is a plumbing command for retreiving the raw bytes stored in
84 85
a DAG node. It outputs to stdout, and <key> is a base58 encoded
multihash.
86 87 88 89

Note that the "--encoding" option does not affect the output, since the
output is the raw data of the object.
`,
90
	},
91 92

	Arguments: []cmds.Argument{
93
		cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(),
94
	},
95
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
96
		n, err := req.InvocContext().GetNode()
97
		if err != nil {
98 99
			res.SetError(err, cmds.ErrNormal)
			return
100
		}
101

Jeromy's avatar
Jeromy committed
102
		fpath := path.Path(req.Arguments()[0])
Jeromy's avatar
Jeromy committed
103
		node, err := core.Resolve(req.Context(), n, fpath)
104 105 106 107
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
108
		res.SetOutput(bytes.NewReader(node.Data))
109 110 111 112
	},
}

var objectLinksCmd = &cmds.Command{
113 114 115
	Helptext: cmds.HelpText{
		Tagline: "Outputs the links pointed to by the specified object",
		ShortDescription: `
Matt Bell's avatar
Matt Bell committed
116 117 118
'ipfs object links' is a plumbing command for retreiving the links from
a DAG node. It outputs to stdout, and <key> is a base58 encoded
multihash.
119 120
`,
	},
121 122

	Arguments: []cmds.Argument{
123
		cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(),
124
	},
125
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
126
		n, err := req.InvocContext().GetNode()
127
		if err != nil {
128 129
			res.SetError(err, cmds.ErrNormal)
			return
130
		}
131

Jeromy's avatar
Jeromy committed
132
		fpath := path.Path(req.Arguments()[0])
Jeromy's avatar
Jeromy committed
133
		node, err := core.Resolve(req.Context(), n, fpath)
134 135 136 137 138
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		output, err := getOutput(node)
139 140 141 142 143
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(output)
144
	},
145
	Marshalers: cmds.MarshalerMap{
146
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
147
			object := res.Output().(*Object)
148 149
			buf := new(bytes.Buffer)
			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
150 151 152 153
			fmt.Fprintln(w, "Hash\tSize\tName\t")
			for _, link := range object.Links {
				fmt.Fprintf(w, "%s\t%v\t%s\t\n", link.Hash, link.Size, link.Name)
			}
154
			w.Flush()
155
			return buf, nil
156 157
		},
	},
158
	Type: Object{},
159 160 161
}

var objectGetCmd = &cmds.Command{
162 163 164 165
	Helptext: cmds.HelpText{
		Tagline: "Get and serialize the DAG node named by <key>",
		ShortDescription: `
'ipfs object get' is a plumbing command for retreiving DAG nodes.
166 167
It serializes the DAG node to the format specified by the "--encoding"
flag. It outputs to stdout, and <key> is a base58 encoded multihash.
168 169 170
`,
		LongDescription: `
'ipfs object get' is a plumbing command for retreiving DAG nodes.
171 172
It serializes the DAG node to the format specified by the "--encoding"
flag. It outputs to stdout, and <key> is a base58 encoded multihash.
173

174 175 176 177
This command outputs data in the following encodings:
  * "protobuf"
  * "json"
  * "xml"
178
(Specified by the "--encoding" or "-enc" flag)`,
179
	},
180 181

	Arguments: []cmds.Argument{
182
		cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(),
183
	},
184
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
185
		n, err := req.InvocContext().GetNode()
186
		if err != nil {
187 188
			res.SetError(err, cmds.ErrNormal)
			return
189
		}
190

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

Jeromy's avatar
Jeromy committed
193
		object, err := core.Resolve(req.Context(), n, fpath)
194
		if err != nil {
195 196
			res.SetError(err, cmds.ErrNormal)
			return
197 198
		}

199 200
		node := &Node{
			Links: make([]Link, len(object.Links)),
201
			Data:  string(object.Data),
202 203 204 205 206 207 208 209 210 211
		}

		for i, link := range object.Links {
			node.Links[i] = Link{
				Hash: link.Hash.B58String(),
				Name: link.Name,
				Size: link.Size,
			}
		}

212
		res.SetOutput(node)
213
	},
214
	Type: Node{},
215
	Marshalers: cmds.MarshalerMap{
216
		cmds.EncodingType("protobuf"): func(res cmds.Response) (io.Reader, error) {
217
			node := res.Output().(*Node)
218 219 220
			object, err := deserializeNode(node)
			if err != nil {
				return nil, err
221
			}
222 223 224 225 226 227

			marshaled, err := object.Marshal()
			if err != nil {
				return nil, err
			}
			return bytes.NewReader(marshaled), nil
228 229 230 231
		},
	},
}

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
var objectStatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Get stats for the DAG node named by <key>",
		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
`,
	},

	Arguments: []cmds.Argument{
248
		cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(),
249
	},
250
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
251
		n, err := req.InvocContext().GetNode()
252
		if err != nil {
253 254
			res.SetError(err, cmds.ErrNormal)
			return
255 256
		}

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

Jeromy's avatar
Jeromy committed
259
		object, err := core.Resolve(req.Context(), n, fpath)
260
		if err != nil {
261 262
			res.SetError(err, cmds.ErrNormal)
			return
263 264 265 266
		}

		ns, err := object.Stat()
		if err != nil {
267 268
			res.SetError(err, cmds.ErrNormal)
			return
269 270
		}

271
		res.SetOutput(ns)
272 273 274 275
	},
	Type: dag.NodeStat{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
276
			ns := res.Output().(*dag.NodeStat)
277

278
			buf := new(bytes.Buffer)
279
			w := func(s string, n int) {
280
				fmt.Fprintf(buf, "%s: %d\n", s, n)
281 282 283 284 285 286 287
			}
			w("NumLinks", ns.NumLinks)
			w("BlockSize", ns.BlockSize)
			w("LinksSize", ns.LinksSize)
			w("DataSize", ns.DataSize)
			w("CumulativeSize", ns.CumulativeSize)

288
			return buf, nil
289 290 291 292
		},
	},
}

293
var objectPutCmd = &cmds.Command{
294 295 296 297 298 299 300 301
	Helptext: cmds.HelpText{
		Tagline: "Stores input as a DAG object, outputs its key",
		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.
302 303
It reads from stdin, and the output is a base58 encoded multihash.

Henry's avatar
Henry committed
304 305
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
306
	* "protobuf"
Henry's avatar
Henry committed
307
	* "json" (default)
Dylan Powers's avatar
Dylan Powers committed
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327

Examples:

	echo '{ "Data": "abc" }' | ipfs object put

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:

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

and then run

	ipfs object put node.json
328
`,
329
	},
330 331

	Arguments: []cmds.Argument{
Henry's avatar
Henry committed
332 333 334 335
		cmds.FileArg("data", true, false, "Data to be stored as a DAG object").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.StringOption("inputenc", "Encoding type of input data, either \"protobuf\" or \"json\""),
336
	},
337
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
338
		n, err := req.InvocContext().GetNode()
339
		if err != nil {
340 341
			res.SetError(err, cmds.ErrNormal)
			return
342
		}
343

344 345
		input, err := req.Files().NextFile()
		if err != nil && err != io.EOF {
346 347
			res.SetError(err, cmds.ErrNormal)
			return
348 349
		}

Henry's avatar
Henry committed
350 351 352 353 354 355 356 357
		inputenc, found, err := req.Option("inputenc").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if !found {
			inputenc = "json"
		}
358

Henry's avatar
Henry committed
359
		output, err := objectPut(n, input, inputenc)
360 361 362 363 364
		if err != nil {
			errType := cmds.ErrNormal
			if err == ErrUnknownObjectEnc {
				errType = cmds.ErrClient
			}
365 366
			res.SetError(err, errType)
			return
367 368
		}

369
		res.SetOutput(output)
370
	},
371
	Marshalers: cmds.MarshalerMap{
372
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
373
			object := res.Output().(*Object)
374
			return strings.NewReader("added " + object.Hash), nil
375 376
		},
	},
377
	Type: Object{},
378 379
}

380 381 382 383 384 385 386 387 388 389 390
var objectNewCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "creates a new object from an ipfs template",
		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.
391 392 393

Available templates:
	* unixfs-dir
394 395 396 397 398 399
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("template", false, false, "optional template to use"),
	},
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
400
		n, err := req.InvocContext().GetNode()
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		node := new(dag.Node)
		if len(req.Arguments()) == 1 {
			template := req.Arguments()[0]
			var err error
			node, err = nodeFromTemplate(template)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}

		k, err := n.DAG.Add(node)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(&Object{Hash: k.B58String()})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			object := res.Output().(*Object)
			return strings.NewReader(object.Hash + "\n"), nil
		},
	},
	Type: Object{},
}

var objectPatchCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Create a new merkledag object based on an existing one",
		ShortDescription: `
437
'ipfs object patch <root> <cmd> <args>' is a plumbing command used to
438
build custom DAG objects. It adds and removes links from objects, creating a new
439 440 441 442 443 444 445 446 447
object as a result. This is the merkle-dag version of modifying an object. It
can also set the data inside a node with 'set-data' and append to that data as
well with 'append-data'.

Patch commands:
    add-link <name> <ref>     - adds a link to a node
    rm-link <name>            - removes a link from a node
    set-data                  - sets a nodes data from stdin
    append-data               - appends to a nodes data from stdin
448 449 450 451 452

Examples:

    EMPTY_DIR=$(ipfs object new unixfs-dir)
    BAR=$(echo "bar" | ipfs add -q)
453
    ipfs object patch $EMPTY_DIR add-link foo $BAR
454 455 456 457

This takes an empty directory, and adds a link named foo under it, pointing to
a file containing 'bar', and returns the hash of the new object.

458
    ipfs object patch $FOO_BAR rm-link foo
459 460 461

This removes the link named foo from the hash in $FOO_BAR and returns the
resulting object hash.
462 463 464 465 466 467

The data inside the node can be modified as well:

    ipfs object patch $FOO_BAR set-data < file.dat
    ipfs object patch $FOO_BAR append-data < file.dat

468 469
`,
	},
470 471 472
	Options: []cmds.Option{
		cmds.BoolOption("create", "p", "create intermediate directories on add-link"),
	},
473 474 475 476 477
	Arguments: []cmds.Argument{
		cmds.StringArg("root", true, false, "the hash of the node to modify"),
		cmds.StringArg("command", true, false, "the operation to perform"),
		cmds.StringArg("args", true, true, "extra arguments").EnableStdin(),
	},
478
	Type: Object{},
479
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
480
		nd, err := req.InvocContext().GetNode()
481 482 483 484 485
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

486 487 488 489 490
		rootarg := req.Arguments()[0]
		if strings.HasPrefix(rootarg, "/ipfs/") {
			rootarg = rootarg[6:]
		}
		rhash := key.B58KeyDecode(rootarg)
491
		if rhash == "" {
492
			res.SetError(fmt.Errorf("incorrectly formatted root hash: %s", req.Arguments()[0]), cmds.ErrNormal)
493 494 495
			return
		}

496
		rnode, err := nd.DAG.Get(req.Context(), rhash)
497 498 499 500 501 502 503 504 505 506 507 508 509 510
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		action := req.Arguments()[1]

		switch action {
		case "add-link":
			k, err := addLinkCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
511
			res.SetOutput(&Object{Hash: k.B58String()})
512 513 514 515 516 517
		case "rm-link":
			k, err := rmLinkCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
518
			res.SetOutput(&Object{Hash: k.B58String()})
519 520 521 522 523 524
		case "set-data":
			k, err := setDataCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
525
			res.SetOutput(&Object{Hash: k.B58String()})
526 527 528 529 530 531
		case "append-data":
			k, err := appendDataCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
532
			res.SetOutput(&Object{Hash: k.B58String()})
533 534 535 536 537 538 539
		default:
			res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
			return
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
540
			o, ok := res.Output().(*Object)
541 542 543 544
			if !ok {
				return nil, u.ErrCast()
			}

545
			return strings.NewReader(o.Hash + "\n"), nil
546 547 548 549 550 551 552 553 554
		},
	},
}

func appendDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
	if len(req.Arguments()) < 3 {
		return "", fmt.Errorf("not enough arguments for set-data")
	}

Jeromy's avatar
Jeromy committed
555
	nd, err := req.InvocContext().GetNode()
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
	if err != nil {
		return "", err
	}

	root.Data = append(root.Data, []byte(req.Arguments()[2])...)

	newkey, err := nd.DAG.Add(root)
	if err != nil {
		return "", err
	}

	return newkey, nil
}

func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
	if len(req.Arguments()) < 3 {
		return "", fmt.Errorf("not enough arguments for set-data")
	}

Jeromy's avatar
Jeromy committed
575
	nd, err := req.InvocContext().GetNode()
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
	if err != nil {
		return "", err
	}

	root.Data = []byte(req.Arguments()[2])

	newkey, err := nd.DAG.Add(root)
	if err != nil {
		return "", err
	}

	return newkey, nil
}

func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
	if len(req.Arguments()) < 3 {
		return "", fmt.Errorf("not enough arguments for rm-link")
	}

Jeromy's avatar
Jeromy committed
595
	nd, err := req.InvocContext().GetNode()
596 597 598 599
	if err != nil {
		return "", err
	}

Jeromy's avatar
Jeromy committed
600 601 602
	path := req.Arguments()[2]

	e := dagutils.NewDagEditor(nd.DAG, root)
603

Jeromy's avatar
Jeromy committed
604
	err = e.RmLink(req.Context(), path)
605 606 607 608
	if err != nil {
		return "", err
	}

Jeromy's avatar
Jeromy committed
609 610
	nnode := e.GetNode()

Jeromy's avatar
Jeromy committed
611 612 613
	return nnode.Key()
}

614 615 616 617 618
func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
	if len(req.Arguments()) < 4 {
		return "", fmt.Errorf("not enough arguments for add-link")
	}

Jeromy's avatar
Jeromy committed
619
	nd, err := req.InvocContext().GetNode()
620 621 622 623
	if err != nil {
		return "", err
	}

Jeromy's avatar
Jeromy committed
624
	path := req.Arguments()[2]
625 626
	childk := key.B58KeyDecode(req.Arguments()[3])

627 628 629 630 631
	create, _, err := req.Option("create").Bool()
	if err != nil {
		return "", err
	}

Jeromy's avatar
Jeromy committed
632 633 634 635 636 637 638 639 640
	var createfunc func() *dag.Node
	if create {
		createfunc = func() *dag.Node {
			return &dag.Node{Data: ft.FolderPBData()}
		}
	}

	e := dagutils.NewDagEditor(nd.DAG, root)

641 642 643 644 645 646
	childnd, err := nd.DAG.Get(req.Context(), childk)
	if err != nil {
		return "", err
	}

	err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc)
647 648 649
	if err != nil {
		return "", err
	}
Jeromy's avatar
Jeromy committed
650 651 652

	nnode := e.GetNode()

Jeromy's avatar
Jeromy committed
653
	return nnode.Key()
654 655 656 657 658 659 660 661 662 663 664 665 666
}

func nodeFromTemplate(template string) (*dag.Node, error) {
	switch template {
	case "unixfs-dir":
		nd := new(dag.Node)
		nd.Data = ft.FolderPBData()
		return nd, nil
	default:
		return nil, fmt.Errorf("template '%s' not found", template)
	}
}

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

670 671 672
// objectPut takes a format option, serializes bytes from stdin and updates the dag with that data
func objectPut(n *core.IpfsNode, input io.Reader, encoding string) (*Object, error) {

673
	data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
674 675 676 677 678 679 680 681
	if err != nil {
		return nil, err
	}

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

682
	var dagnode *dag.Node
683 684
	switch getObjectEnc(encoding) {
	case objectEncodingJSON:
685 686 687 688 689 690
		node := new(Node)
		err = json.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}

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

697 698 699 700
		dagnode, err = deserializeNode(node)
		if err != nil {
			return nil, err
		}
701 702 703 704

	case objectEncodingProtobuf:
		dagnode, err = dag.Decoded(data)

705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
	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
		}

		dagnode, err = deserializeNode(node)
		if err != nil {
			return nil, err
		}

723 724 725 726 727 728 729 730
	default:
		return nil, ErrUnknownObjectEnc
	}

	if err != nil {
		return nil, err
	}

731
	_, err = n.DAG.Add(dagnode)
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
	if err != nil {
		return nil, err
	}

	return getOutput(dagnode)
}

// 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"
747
	objectEncodingXML                     = "xml"
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
)

func getObjectEnc(o interface{}) objectEncoding {
	v, ok := o.(string)
	if !ok {
		// chosen as default because it's human readable
		log.Warning("option is not a string - falling back to json")
		return objectEncodingJSON
	}

	return objectEncoding(v)
}

func getOutput(dagnode *dag.Node) (*Object, error) {
	key, err := dagnode.Key()
	if err != nil {
		return nil, err
	}

	output := &Object{
		Hash:  key.Pretty(),
		Links: make([]Link, len(dagnode.Links)),
	}

	for i, link := range dagnode.Links {
		output.Links[i] = Link{
			Name: link.Name,
			Hash: link.Hash.B58String(),
			Size: link.Size,
		}
	}

	return output, nil
}
782 783 784 785

// converts the Node object into a real dag.Node
func deserializeNode(node *Node) (*dag.Node, error) {
	dagnode := new(dag.Node)
786
	dagnode.Data = []byte(node.Data)
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
	dagnode.Links = make([]*dag.Link, len(node.Links))
	for i, link := range node.Links {
		hash, err := mh.FromB58String(link.Hash)
		if err != nil {
			return nil, err
		}
		dagnode.Links[i] = &dag.Link{
			Name: link.Name,
			Size: link.Size,
			Hash: hash,
		}
	}

	return dagnode, nil
}
802 803 804 805

func NodeEmpty(node *Node) bool {
	return (node.Data == "" && len(node.Links) == 0)
}