object.go 19.6 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) {
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> [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

This removes the link named foo from the hash in $FOO_BAR and returns the
resulting object hash.
`,
	},
456 457 458
	Options: []cmds.Option{
		cmds.BoolOption("create", "p", "create intermediate directories on add-link"),
	},
459 460 461 462 463
	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(),
	},
464
	Type: Object{},
465
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
466
		nd, err := req.InvocContext().GetNode()
467 468 469 470 471 472 473 474 475 476 477
		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
		}

Jeromy's avatar
Jeromy committed
478
		ctx, cancel := context.WithTimeout(req.Context(), time.Second*30)
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
		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
			}
496
			res.SetOutput(&Object{Hash: k.B58String()})
497 498 499 500 501 502
		case "rm-link":
			k, err := rmLinkCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
503
			res.SetOutput(&Object{Hash: k.B58String()})
504 505 506 507 508 509
		case "set-data":
			k, err := setDataCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
510
			res.SetOutput(&Object{Hash: k.B58String()})
511 512 513 514 515 516
		case "append-data":
			k, err := appendDataCaller(req, rnode)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
517
			res.SetOutput(&Object{Hash: k.B58String()})
518 519 520 521 522 523 524
		default:
			res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
			return
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
525
			o, ok := res.Output().(*Object)
526 527 528 529
			if !ok {
				return nil, u.ErrCast()
			}

530
			return strings.NewReader(o.Hash + "\n"), nil
531 532 533 534 535 536 537 538 539
		},
	},
}

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
540
	nd, err := req.InvocContext().GetNode()
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
	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
560
	nd, err := req.InvocContext().GetNode()
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579
	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
580
	nd, err := req.InvocContext().GetNode()
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
	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")
	}

Jeromy's avatar
Jeromy committed
605
	nd, err := req.InvocContext().GetNode()
606 607 608 609
	if err != nil {
		return "", err
	}

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

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

615 616 617 618 619 620
	create, _, err := req.Option("create").Bool()
	if err != nil {
		return "", err
	}

	nnode, err := insertNodeAtPath(req.Context(), nd.DAG, root, parts, childk, create)
621 622 623
	if err != nil {
		return "", err
	}
Jeromy's avatar
Jeromy committed
624
	return nnode.Key()
625 626
}

Jeromy's avatar
Jeromy committed
627
func addLink(ctx context.Context, ds dag.DAGService, root *dag.Node, childname string, childk key.Key) (*dag.Node, error) {
628
	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
629
	defer cancel()
630 631
	childnd, err := ds.Get(ctx, childk)
	if err != nil {
Jeromy's avatar
Jeromy committed
632
		return nil, err
633
	}
634 635 636

	// ensure no link with that name already exists
	_ = root.RemoveNodeLink(childname) // ignore error, only option is ErrNotFound
637 638 639

	err = root.AddNodeLinkClean(childname, childnd)
	if err != nil {
Jeromy's avatar
Jeromy committed
640
		return nil, err
641 642
	}

Jeromy's avatar
Jeromy committed
643
	_, err = ds.Add(root)
644
	if err != nil {
Jeromy's avatar
Jeromy committed
645
		return nil, err
646
	}
Jeromy's avatar
Jeromy committed
647 648 649
	return root, nil
}

650
func insertNodeAtPath(ctx context.Context, ds dag.DAGService, root *dag.Node, path []string, toinsert key.Key, create bool) (*dag.Node, error) {
Jeromy's avatar
Jeromy committed
651 652 653 654
	if len(path) == 1 {
		return addLink(ctx, ds, root, path[0], toinsert)
	}

655
	var nd *dag.Node
Jeromy's avatar
Jeromy committed
656 657
	child, err := root.GetNodeLink(path[0])
	if err != nil {
658 659 660 661 662 663 664 665 666 667 668
		// if 'create' is true, we create directories on the way down as needed
		if err == dag.ErrNotFound && create {
			nd = &dag.Node{Data: ft.FolderPBData()}
		} else {
			return nil, err
		}
	} else {
		nd, err = child.GetNode(ctx, ds)
		if err != nil {
			return nil, err
		}
Jeromy's avatar
Jeromy committed
669 670
	}

671
	ndprime, err := insertNodeAtPath(ctx, ds, nd, path[1:], toinsert, create)
Jeromy's avatar
Jeromy committed
672 673 674 675
	if err != nil {
		return nil, err
	}

676
	_ = root.RemoveNodeLink(path[0])
Jeromy's avatar
Jeromy committed
677 678 679 680 681 682 683 684 685 686 687
	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
688 689 690 691 692 693 694 695 696 697 698 699 700
}

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

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

704 705 706
// 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) {

707
	data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
708 709 710 711 712 713 714 715
	if err != nil {
		return nil, err
	}

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

716
	var dagnode *dag.Node
717 718
	switch getObjectEnc(encoding) {
	case objectEncodingJSON:
719 720 721 722 723 724
		node := new(Node)
		err = json.Unmarshal(data, node)
		if err != nil {
			return nil, err
		}

725 726 727 728 729 730
		// 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
		}

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

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

	default:
		return nil, ErrUnknownObjectEnc
	}

	if err != nil {
		return nil, err
	}

747
	_, err = n.DAG.Add(dagnode)
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 786 787 788 789 790 791 792 793 794 795 796
	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
}
797 798 799 800

// converts the Node object into a real dag.Node
func deserializeNode(node *Node) (*dag.Node, error) {
	dagnode := new(dag.Node)
801
	dagnode.Data = []byte(node.Data)
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
	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
}