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

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

14
	mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
15
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
16

17
	key "github.com/ipfs/go-ipfs/blocks/key"
18 19 20 21
	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
	dag "github.com/ipfs/go-ipfs/merkledag"
	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) {
96 97
		n, err := req.Context().GetNode()
		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])
103
		node, err := core.Resolve(req.Context().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) {
126 127
		n, err := req.Context().GetNode()
		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])
133 134 135 136 137 138
		node, err := core.Resolve(req.Context().Context, n, fpath)
		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) {
185 186
		n, err := req.Context().GetNode()
		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

193
		object, err := core.Resolve(req.Context().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) {
251 252
		n, err := req.Context().GetNode()
		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

259
		object, err := core.Resolve(req.Context().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) {
338 339
		n, err := req.Context().GetNode()
		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 400 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
`,
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("template", false, false, "optional template to use"),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.Context().GetNode()
		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> [add-link|rm-link] <args>' is a plumbing command used to
438 439 440 441 442 443 444
build custom DAG objects. It adds and removes links from objects, creating a new
object as a result. This is the merkle-dag version of modifying an object.

Examples:

    EMPTY_DIR=$(ipfs object new unixfs-dir)
    BAR=$(echo "bar" | ipfs add -q)
445
    ipfs object patch $EMPTY_DIR add-link foo $BAR
446 447 448 449

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.

450
    ipfs object patch $FOO_BAR rm-link foo
451 452 453 454 455 456 457 458 459 460 461

This removes the link named foo from the hash in $FOO_BAR and returns the
resulting object hash.
`,
	},
	Options: []cmds.Option{},
	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(),
	},
462
	Type: Object{},
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.Context().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		rhash := key.B58KeyDecode(req.Arguments()[0])
		if rhash == "" {
			res.SetError(fmt.Errorf("incorrectly formatted root hash"), cmds.ErrNormal)
			return
		}

		ctx, cancel := context.WithTimeout(req.Context().Context, time.Second*30)
		rnode, err := nd.DAG.Get(ctx, rhash)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			cancel()
			return
		}
		cancel()

		action := req.Arguments()[1]

		switch action {
		case "add-link":
			k, err := addLinkCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
494
			res.SetOutput(&Object{Hash: k.B58String()})
495 496 497 498 499 500
		case "rm-link":
			k, err := rmLinkCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
501
			res.SetOutput(&Object{Hash: k.B58String()})
502 503 504 505 506 507
		case "set-data":
			k, err := setDataCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
508
			res.SetOutput(&Object{Hash: k.B58String()})
509 510 511 512 513 514
		case "append-data":
			k, err := appendDataCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
515
			res.SetOutput(&Object{Hash: k.B58String()})
516 517 518 519 520 521 522
		default:
			res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
			return
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
523
			o, ok := res.Output().(*Object)
524 525 526 527
			if !ok {
				return nil, u.ErrCast()
			}

528
			return strings.NewReader(o.Hash + "\n"), nil
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
		},
	},
}

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")
	}

	nd, err := req.Context().GetNode()
	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")
	}

	nd, err := req.Context().GetNode()
	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")
	}

	nd, err := req.Context().GetNode()
	if err != nil {
		return "", err
	}

	name := req.Arguments()[2]

	err = root.RemoveNodeLink(name)
	if err != nil {
		return "", err
	}

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

	return newkey, nil
}

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")
	}

	nd, err := req.Context().GetNode()
	if err != nil {
		return "", err
	}

Jeromy's avatar
Jeromy committed
608
	path := req.Arguments()[2]
609 610
	childk := key.B58KeyDecode(req.Arguments()[3])

Jeromy's avatar
Jeromy committed
611 612 613
	parts := strings.Split(path, "/")

	nnode, err := insertNodeAtPath(req.Context().Context, nd.DAG, root, parts, childk)
614 615 616
	if err != nil {
		return "", err
	}
Jeromy's avatar
Jeromy committed
617
	return nnode.Key()
618 619
}

Jeromy's avatar
Jeromy committed
620
func addLink(ctx context.Context, ds dag.DAGService, root *dag.Node, childname string, childk key.Key) (*dag.Node, error) {
621 622 623 624
	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
	childnd, err := ds.Get(ctx, childk)
	if err != nil {
		cancel()
Jeromy's avatar
Jeromy committed
625
		return nil, err
626 627 628 629 630
	}
	cancel()

	err = root.AddNodeLinkClean(childname, childnd)
	if err != nil {
Jeromy's avatar
Jeromy committed
631
		return nil, err
632 633
	}

Jeromy's avatar
Jeromy committed
634
	_, err = ds.Add(root)
635
	if err != nil {
Jeromy's avatar
Jeromy committed
636
		return nil, err
637
	}
Jeromy's avatar
Jeromy committed
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676
	return root, nil
}

func insertNodeAtPath(ctx context.Context, ds dag.DAGService, root *dag.Node, path []string, toinsert key.Key) (*dag.Node, error) {
	if len(path) == 1 {
		return addLink(ctx, ds, root, path[0], toinsert)
	}

	child, err := root.GetNodeLink(path[0])
	if err != nil {
		return nil, err
	}

	nd, err := child.GetNode(ctx, ds)
	if err != nil {
		return nil, err
	}

	ndprime, err := insertNodeAtPath(ctx, ds, nd, path[1:], toinsert)
	if err != nil {
		return nil, err
	}

	err = root.RemoveNodeLink(path[0])
	if err != nil {
		return nil, err
	}

	err = root.AddNodeLinkClean(path[0], ndprime)
	if err != nil {
		return nil, err
	}

	_, err = ds.Add(root)
	if err != nil {
		return nil, err
	}

	return root, nil
677 678 679 680 681 682 683 684 685 686 687 688 689
}

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

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

693 694 695
// 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) {

696
	data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
697 698 699 700 701 702 703 704
	if err != nil {
		return nil, err
	}

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

705
	var dagnode *dag.Node
706 707
	switch getObjectEnc(encoding) {
	case objectEncodingJSON:
708 709 710 711 712 713
		node := new(Node)
		err = json.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}

714 715 716 717 718 719
		// check that we have data in the Node to add
		// otherwise we will add the empty object without raising an error
		if node.Data == "" && len(node.Links) == 0 {
			return nil, ErrEmptyNode
		}

720 721 722 723
		dagnode, err = deserializeNode(node)
		if err != nil {
			return nil, err
		}
724 725 726 727 728 729 730 731 732 733 734 735

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

	default:
		return nil, ErrUnknownObjectEnc
	}

	if err != nil {
		return nil, err
	}

736
	_, err = n.DAG.Add(dagnode)
737 738 739 740 741 742 743 744 745 746 747 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 782 783 784 785
	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"
)

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
}
786 787 788 789

// converts the Node object into a real dag.Node
func deserializeNode(node *Node) (*dag.Node, error) {
	dagnode := new(dag.Node)
790
	dagnode.Data = []byte(node.Data)
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
	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
}