github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/core/commands/object.go (about) 1 package commands 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "strings" 11 "text/tabwriter" 12 13 mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" 14 15 key "github.com/ipfs/go-ipfs/blocks/key" 16 cmds "github.com/ipfs/go-ipfs/commands" 17 core "github.com/ipfs/go-ipfs/core" 18 dag "github.com/ipfs/go-ipfs/merkledag" 19 dagutils "github.com/ipfs/go-ipfs/merkledag/utils" 20 path "github.com/ipfs/go-ipfs/path" 21 ft "github.com/ipfs/go-ipfs/unixfs" 22 u "github.com/ipfs/go-ipfs/util" 23 ) 24 25 // ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k 26 var ErrObjectTooLarge = errors.New("input object was too large. limit is 512kbytes") 27 28 const inputLimit = 512 * 1024 29 30 type Node struct { 31 Links []Link 32 Data string 33 } 34 35 type Link struct { 36 Name, Hash string 37 Size uint64 38 } 39 40 type Object struct { 41 Hash string 42 Links []Link 43 } 44 45 var ObjectCmd = &cmds.Command{ 46 Helptext: cmds.HelpText{ 47 Tagline: "Interact with ipfs objects", 48 ShortDescription: ` 49 'ipfs object' is a plumbing command used to manipulate DAG objects 50 directly.`, 51 Synopsis: ` 52 ipfs object get <key> - Get the DAG node named by <key> 53 ipfs object put <data> - Stores input, outputs its key 54 ipfs object data <key> - Outputs raw bytes in an object 55 ipfs object links <key> - Outputs links pointed to by object 56 ipfs object stat <key> - Outputs statistics of object 57 ipfs object new <template> - Create new ipfs objects 58 ipfs object patch <args> - Create new object from old ones 59 `, 60 }, 61 62 Subcommands: map[string]*cmds.Command{ 63 "data": objectDataCmd, 64 "links": objectLinksCmd, 65 "get": objectGetCmd, 66 "put": objectPutCmd, 67 "stat": objectStatCmd, 68 "new": objectNewCmd, 69 "patch": objectPatchCmd, 70 }, 71 } 72 73 var objectDataCmd = &cmds.Command{ 74 Helptext: cmds.HelpText{ 75 Tagline: "Outputs the raw bytes in an IPFS object", 76 ShortDescription: ` 77 ipfs object data is a plumbing command for retreiving the raw bytes stored in 78 a DAG node. It outputs to stdout, and <key> is a base58 encoded 79 multihash. 80 `, 81 LongDescription: ` 82 ipfs object data is a plumbing command for retreiving the raw bytes stored in 83 a DAG node. It outputs to stdout, and <key> is a base58 encoded 84 multihash. 85 86 Note that the "--encoding" option does not affect the output, since the 87 output is the raw data of the object. 88 `, 89 }, 90 91 Arguments: []cmds.Argument{ 92 cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(), 93 }, 94 Run: func(req cmds.Request, res cmds.Response) { 95 n, err := req.InvocContext().GetNode() 96 if err != nil { 97 res.SetError(err, cmds.ErrNormal) 98 return 99 } 100 101 fpath := path.Path(req.Arguments()[0]) 102 node, err := core.Resolve(req.Context(), n, fpath) 103 if err != nil { 104 res.SetError(err, cmds.ErrNormal) 105 return 106 } 107 res.SetOutput(bytes.NewReader(node.Data)) 108 }, 109 } 110 111 var objectLinksCmd = &cmds.Command{ 112 Helptext: cmds.HelpText{ 113 Tagline: "Outputs the links pointed to by the specified object", 114 ShortDescription: ` 115 'ipfs object links' is a plumbing command for retreiving the links from 116 a DAG node. It outputs to stdout, and <key> is a base58 encoded 117 multihash. 118 `, 119 }, 120 121 Arguments: []cmds.Argument{ 122 cmds.StringArg("key", true, false, "Key of the object to retrieve, in base58-encoded multihash format").EnableStdin(), 123 }, 124 Run: func(req cmds.Request, res cmds.Response) { 125 n, err := req.InvocContext().GetNode() 126 if err != nil { 127 res.SetError(err, cmds.ErrNormal) 128 return 129 } 130 131 fpath := path.Path(req.Arguments()[0]) 132 node, err := core.Resolve(req.Context(), n, fpath) 133 if err != nil { 134 res.SetError(err, cmds.ErrNormal) 135 return 136 } 137 output, err := getOutput(node) 138 if err != nil { 139 res.SetError(err, cmds.ErrNormal) 140 return 141 } 142 res.SetOutput(output) 143 }, 144 Marshalers: cmds.MarshalerMap{ 145 cmds.Text: func(res cmds.Response) (io.Reader, error) { 146 object := res.Output().(*Object) 147 buf := new(bytes.Buffer) 148 w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0) 149 fmt.Fprintln(w, "Hash\tSize\tName\t") 150 for _, link := range object.Links { 151 fmt.Fprintf(w, "%s\t%v\t%s\t\n", link.Hash, link.Size, link.Name) 152 } 153 w.Flush() 154 return buf, nil 155 }, 156 }, 157 Type: Object{}, 158 } 159 160 var objectGetCmd = &cmds.Command{ 161 Helptext: cmds.HelpText{ 162 Tagline: "Get and serialize the DAG node named by <key>", 163 ShortDescription: ` 164 'ipfs object get' is a plumbing command for retreiving DAG nodes. 165 It serializes the DAG node to the format specified by the "--encoding" 166 flag. It outputs to stdout, and <key> is a base58 encoded multihash. 167 `, 168 LongDescription: ` 169 'ipfs object get' is a plumbing command for retreiving DAG nodes. 170 It serializes the DAG node to the format specified by the "--encoding" 171 flag. It outputs to stdout, and <key> is a base58 encoded multihash. 172 173 This command outputs data in the following encodings: 174 * "protobuf" 175 * "json" 176 * "xml" 177 (Specified by the "--encoding" or "-enc" flag)`, 178 }, 179 180 Arguments: []cmds.Argument{ 181 cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(), 182 }, 183 Run: func(req cmds.Request, res cmds.Response) { 184 n, err := req.InvocContext().GetNode() 185 if err != nil { 186 res.SetError(err, cmds.ErrNormal) 187 return 188 } 189 190 fpath := path.Path(req.Arguments()[0]) 191 192 object, err := core.Resolve(req.Context(), n, fpath) 193 if err != nil { 194 res.SetError(err, cmds.ErrNormal) 195 return 196 } 197 198 node := &Node{ 199 Links: make([]Link, len(object.Links)), 200 Data: string(object.Data), 201 } 202 203 for i, link := range object.Links { 204 node.Links[i] = Link{ 205 Hash: link.Hash.B58String(), 206 Name: link.Name, 207 Size: link.Size, 208 } 209 } 210 211 res.SetOutput(node) 212 }, 213 Type: Node{}, 214 Marshalers: cmds.MarshalerMap{ 215 cmds.EncodingType("protobuf"): func(res cmds.Response) (io.Reader, error) { 216 node := res.Output().(*Node) 217 object, err := deserializeNode(node) 218 if err != nil { 219 return nil, err 220 } 221 222 marshaled, err := object.Marshal() 223 if err != nil { 224 return nil, err 225 } 226 return bytes.NewReader(marshaled), nil 227 }, 228 }, 229 } 230 231 var objectStatCmd = &cmds.Command{ 232 Helptext: cmds.HelpText{ 233 Tagline: "Get stats for the DAG node named by <key>", 234 ShortDescription: ` 235 'ipfs object stat' is a plumbing command to print DAG node statistics. 236 <key> is a base58 encoded multihash. It outputs to stdout: 237 238 NumLinks int number of links in link table 239 BlockSize int size of the raw, encoded data 240 LinksSize int size of the links segment 241 DataSize int size of the data segment 242 CumulativeSize int cumulative size of object and its references 243 `, 244 }, 245 246 Arguments: []cmds.Argument{ 247 cmds.StringArg("key", true, false, "Key of the object to retrieve (in base58-encoded multihash format)").EnableStdin(), 248 }, 249 Run: func(req cmds.Request, res cmds.Response) { 250 n, err := req.InvocContext().GetNode() 251 if err != nil { 252 res.SetError(err, cmds.ErrNormal) 253 return 254 } 255 256 fpath := path.Path(req.Arguments()[0]) 257 258 object, err := core.Resolve(req.Context(), n, fpath) 259 if err != nil { 260 res.SetError(err, cmds.ErrNormal) 261 return 262 } 263 264 ns, err := object.Stat() 265 if err != nil { 266 res.SetError(err, cmds.ErrNormal) 267 return 268 } 269 270 res.SetOutput(ns) 271 }, 272 Type: dag.NodeStat{}, 273 Marshalers: cmds.MarshalerMap{ 274 cmds.Text: func(res cmds.Response) (io.Reader, error) { 275 ns := res.Output().(*dag.NodeStat) 276 277 buf := new(bytes.Buffer) 278 w := func(s string, n int) { 279 fmt.Fprintf(buf, "%s: %d\n", s, n) 280 } 281 w("NumLinks", ns.NumLinks) 282 w("BlockSize", ns.BlockSize) 283 w("LinksSize", ns.LinksSize) 284 w("DataSize", ns.DataSize) 285 w("CumulativeSize", ns.CumulativeSize) 286 287 return buf, nil 288 }, 289 }, 290 } 291 292 var objectPutCmd = &cmds.Command{ 293 Helptext: cmds.HelpText{ 294 Tagline: "Stores input as a DAG object, outputs its key", 295 ShortDescription: ` 296 'ipfs object put' is a plumbing command for storing DAG nodes. 297 It reads from stdin, and the output is a base58 encoded multihash. 298 `, 299 LongDescription: ` 300 'ipfs object put' is a plumbing command for storing DAG nodes. 301 It reads from stdin, and the output is a base58 encoded multihash. 302 303 Data should be in the format specified by the --inputenc flag. 304 --inputenc may be one of the following: 305 * "protobuf" 306 * "json" (default) 307 308 Examples: 309 310 echo '{ "Data": "abc" }' | ipfs object put 311 312 This creates a node with the data "abc" and no links. For an object with links, 313 create a file named node.json with the contents: 314 315 { 316 "Data": "another", 317 "Links": [ { 318 "Name": "some link", 319 "Hash": "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V", 320 "Size": 8 321 } ] 322 } 323 324 and then run 325 326 ipfs object put node.json 327 `, 328 }, 329 330 Arguments: []cmds.Argument{ 331 cmds.FileArg("data", true, false, "Data to be stored as a DAG object").EnableStdin(), 332 }, 333 Options: []cmds.Option{ 334 cmds.StringOption("inputenc", "Encoding type of input data, either \"protobuf\" or \"json\""), 335 }, 336 Run: func(req cmds.Request, res cmds.Response) { 337 n, err := req.InvocContext().GetNode() 338 if err != nil { 339 res.SetError(err, cmds.ErrNormal) 340 return 341 } 342 343 input, err := req.Files().NextFile() 344 if err != nil && err != io.EOF { 345 res.SetError(err, cmds.ErrNormal) 346 return 347 } 348 349 inputenc, found, err := req.Option("inputenc").String() 350 if err != nil { 351 res.SetError(err, cmds.ErrNormal) 352 return 353 } 354 if !found { 355 inputenc = "json" 356 } 357 358 output, err := objectPut(n, input, inputenc) 359 if err != nil { 360 errType := cmds.ErrNormal 361 if err == ErrUnknownObjectEnc { 362 errType = cmds.ErrClient 363 } 364 res.SetError(err, errType) 365 return 366 } 367 368 res.SetOutput(output) 369 }, 370 Marshalers: cmds.MarshalerMap{ 371 cmds.Text: func(res cmds.Response) (io.Reader, error) { 372 object := res.Output().(*Object) 373 return strings.NewReader("added " + object.Hash), nil 374 }, 375 }, 376 Type: Object{}, 377 } 378 379 var objectNewCmd = &cmds.Command{ 380 Helptext: cmds.HelpText{ 381 Tagline: "creates a new object from an ipfs template", 382 ShortDescription: ` 383 'ipfs object new' is a plumbing command for creating new DAG nodes. 384 `, 385 LongDescription: ` 386 'ipfs object new' is a plumbing command for creating new DAG nodes. 387 By default it creates and returns a new empty merkledag node, but 388 you may pass an optional template argument to create a preformatted 389 node. 390 391 Available templates: 392 * unixfs-dir 393 `, 394 }, 395 Arguments: []cmds.Argument{ 396 cmds.StringArg("template", false, false, "optional template to use"), 397 }, 398 Run: func(req cmds.Request, res cmds.Response) { 399 n, err := req.InvocContext().GetNode() 400 if err != nil { 401 res.SetError(err, cmds.ErrNormal) 402 return 403 } 404 405 node := new(dag.Node) 406 if len(req.Arguments()) == 1 { 407 template := req.Arguments()[0] 408 var err error 409 node, err = nodeFromTemplate(template) 410 if err != nil { 411 res.SetError(err, cmds.ErrNormal) 412 return 413 } 414 } 415 416 k, err := n.DAG.Add(node) 417 if err != nil { 418 res.SetError(err, cmds.ErrNormal) 419 return 420 } 421 res.SetOutput(&Object{Hash: k.B58String()}) 422 }, 423 Marshalers: cmds.MarshalerMap{ 424 cmds.Text: func(res cmds.Response) (io.Reader, error) { 425 object := res.Output().(*Object) 426 return strings.NewReader(object.Hash + "\n"), nil 427 }, 428 }, 429 Type: Object{}, 430 } 431 432 var objectPatchCmd = &cmds.Command{ 433 Helptext: cmds.HelpText{ 434 Tagline: "Create a new merkledag object based on an existing one", 435 ShortDescription: ` 436 'ipfs object patch <root> [add-link|rm-link] <args>' is a plumbing command used to 437 build custom DAG objects. It adds and removes links from objects, creating a new 438 object as a result. This is the merkle-dag version of modifying an object. 439 440 Examples: 441 442 EMPTY_DIR=$(ipfs object new unixfs-dir) 443 BAR=$(echo "bar" | ipfs add -q) 444 ipfs object patch $EMPTY_DIR add-link foo $BAR 445 446 This takes an empty directory, and adds a link named foo under it, pointing to 447 a file containing 'bar', and returns the hash of the new object. 448 449 ipfs object patch $FOO_BAR rm-link foo 450 451 This removes the link named foo from the hash in $FOO_BAR and returns the 452 resulting object hash. 453 `, 454 }, 455 Options: []cmds.Option{ 456 cmds.BoolOption("create", "p", "create intermediate directories on add-link"), 457 }, 458 Arguments: []cmds.Argument{ 459 cmds.StringArg("root", true, false, "the hash of the node to modify"), 460 cmds.StringArg("command", true, false, "the operation to perform"), 461 cmds.StringArg("args", true, true, "extra arguments").EnableStdin(), 462 }, 463 Type: Object{}, 464 Run: func(req cmds.Request, res cmds.Response) { 465 nd, err := req.InvocContext().GetNode() 466 if err != nil { 467 res.SetError(err, cmds.ErrNormal) 468 return 469 } 470 471 rootarg := req.Arguments()[0] 472 if strings.HasPrefix(rootarg, "/ipfs/") { 473 rootarg = rootarg[6:] 474 } 475 rhash := key.B58KeyDecode(rootarg) 476 if rhash == "" { 477 res.SetError(fmt.Errorf("incorrectly formatted root hash: %s", req.Arguments()[0]), cmds.ErrNormal) 478 return 479 } 480 481 rnode, err := nd.DAG.Get(req.Context(), rhash) 482 if err != nil { 483 res.SetError(err, cmds.ErrNormal) 484 return 485 } 486 487 action := req.Arguments()[1] 488 489 switch action { 490 case "add-link": 491 k, err := addLinkCaller(req, rnode) 492 if err != nil { 493 res.SetError(err, cmds.ErrNormal) 494 return 495 } 496 res.SetOutput(&Object{Hash: k.B58String()}) 497 case "rm-link": 498 k, err := rmLinkCaller(req, rnode) 499 if err != nil { 500 res.SetError(err, cmds.ErrNormal) 501 return 502 } 503 res.SetOutput(&Object{Hash: k.B58String()}) 504 case "set-data": 505 k, err := setDataCaller(req, rnode) 506 if err != nil { 507 res.SetError(err, cmds.ErrNormal) 508 return 509 } 510 res.SetOutput(&Object{Hash: k.B58String()}) 511 case "append-data": 512 k, err := appendDataCaller(req, rnode) 513 if err != nil { 514 res.SetError(err, cmds.ErrNormal) 515 return 516 } 517 res.SetOutput(&Object{Hash: k.B58String()}) 518 default: 519 res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal) 520 return 521 } 522 }, 523 Marshalers: cmds.MarshalerMap{ 524 cmds.Text: func(res cmds.Response) (io.Reader, error) { 525 o, ok := res.Output().(*Object) 526 if !ok { 527 return nil, u.ErrCast() 528 } 529 530 return strings.NewReader(o.Hash + "\n"), nil 531 }, 532 }, 533 } 534 535 func appendDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) { 536 if len(req.Arguments()) < 3 { 537 return "", fmt.Errorf("not enough arguments for set-data") 538 } 539 540 nd, err := req.InvocContext().GetNode() 541 if err != nil { 542 return "", err 543 } 544 545 root.Data = append(root.Data, []byte(req.Arguments()[2])...) 546 547 newkey, err := nd.DAG.Add(root) 548 if err != nil { 549 return "", err 550 } 551 552 return newkey, nil 553 } 554 555 func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) { 556 if len(req.Arguments()) < 3 { 557 return "", fmt.Errorf("not enough arguments for set-data") 558 } 559 560 nd, err := req.InvocContext().GetNode() 561 if err != nil { 562 return "", err 563 } 564 565 root.Data = []byte(req.Arguments()[2]) 566 567 newkey, err := nd.DAG.Add(root) 568 if err != nil { 569 return "", err 570 } 571 572 return newkey, nil 573 } 574 575 func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) { 576 if len(req.Arguments()) < 3 { 577 return "", fmt.Errorf("not enough arguments for rm-link") 578 } 579 580 nd, err := req.InvocContext().GetNode() 581 if err != nil { 582 return "", err 583 } 584 585 path := req.Arguments()[2] 586 587 e := dagutils.NewDagEditor(nd.DAG, root) 588 589 err = e.RmLink(req.Context(), path) 590 if err != nil { 591 return "", err 592 } 593 594 nnode := e.GetNode() 595 596 return nnode.Key() 597 } 598 599 func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) { 600 if len(req.Arguments()) < 4 { 601 return "", fmt.Errorf("not enough arguments for add-link") 602 } 603 604 nd, err := req.InvocContext().GetNode() 605 if err != nil { 606 return "", err 607 } 608 609 path := req.Arguments()[2] 610 childk := key.B58KeyDecode(req.Arguments()[3]) 611 612 create, _, err := req.Option("create").Bool() 613 if err != nil { 614 return "", err 615 } 616 617 var createfunc func() *dag.Node 618 if create { 619 createfunc = func() *dag.Node { 620 return &dag.Node{Data: ft.FolderPBData()} 621 } 622 } 623 624 e := dagutils.NewDagEditor(nd.DAG, root) 625 626 childnd, err := nd.DAG.Get(req.Context(), childk) 627 if err != nil { 628 return "", err 629 } 630 631 err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc) 632 if err != nil { 633 return "", err 634 } 635 636 nnode := e.GetNode() 637 638 return nnode.Key() 639 } 640 641 func nodeFromTemplate(template string) (*dag.Node, error) { 642 switch template { 643 case "unixfs-dir": 644 nd := new(dag.Node) 645 nd.Data = ft.FolderPBData() 646 return nd, nil 647 default: 648 return nil, fmt.Errorf("template '%s' not found", template) 649 } 650 } 651 652 // ErrEmptyNode is returned when the input to 'ipfs object put' contains no data 653 var ErrEmptyNode = errors.New("no data or links in this node") 654 655 // objectPut takes a format option, serializes bytes from stdin and updates the dag with that data 656 func objectPut(n *core.IpfsNode, input io.Reader, encoding string) (*Object, error) { 657 658 data, err := ioutil.ReadAll(io.LimitReader(input, inputLimit+10)) 659 if err != nil { 660 return nil, err 661 } 662 663 if len(data) >= inputLimit { 664 return nil, ErrObjectTooLarge 665 } 666 667 var dagnode *dag.Node 668 switch getObjectEnc(encoding) { 669 case objectEncodingJSON: 670 node := new(Node) 671 err = json.Unmarshal(data, node) 672 if err != nil { 673 return nil, err 674 } 675 676 // check that we have data in the Node to add 677 // otherwise we will add the empty object without raising an error 678 if node.Data == "" && len(node.Links) == 0 { 679 return nil, ErrEmptyNode 680 } 681 682 dagnode, err = deserializeNode(node) 683 if err != nil { 684 return nil, err 685 } 686 687 case objectEncodingProtobuf: 688 dagnode, err = dag.Decoded(data) 689 690 default: 691 return nil, ErrUnknownObjectEnc 692 } 693 694 if err != nil { 695 return nil, err 696 } 697 698 _, err = n.DAG.Add(dagnode) 699 if err != nil { 700 return nil, err 701 } 702 703 return getOutput(dagnode) 704 } 705 706 // ErrUnknownObjectEnc is returned if a invalid encoding is supplied 707 var ErrUnknownObjectEnc = errors.New("unknown object encoding") 708 709 type objectEncoding string 710 711 const ( 712 objectEncodingJSON objectEncoding = "json" 713 objectEncodingProtobuf = "protobuf" 714 ) 715 716 func getObjectEnc(o interface{}) objectEncoding { 717 v, ok := o.(string) 718 if !ok { 719 // chosen as default because it's human readable 720 log.Warning("option is not a string - falling back to json") 721 return objectEncodingJSON 722 } 723 724 return objectEncoding(v) 725 } 726 727 func getOutput(dagnode *dag.Node) (*Object, error) { 728 key, err := dagnode.Key() 729 if err != nil { 730 return nil, err 731 } 732 733 output := &Object{ 734 Hash: key.Pretty(), 735 Links: make([]Link, len(dagnode.Links)), 736 } 737 738 for i, link := range dagnode.Links { 739 output.Links[i] = Link{ 740 Name: link.Name, 741 Hash: link.Hash.B58String(), 742 Size: link.Size, 743 } 744 } 745 746 return output, nil 747 } 748 749 // converts the Node object into a real dag.Node 750 func deserializeNode(node *Node) (*dag.Node, error) { 751 dagnode := new(dag.Node) 752 dagnode.Data = []byte(node.Data) 753 dagnode.Links = make([]*dag.Link, len(node.Links)) 754 for i, link := range node.Links { 755 hash, err := mh.FromB58String(link.Hash) 756 if err != nil { 757 return nil, err 758 } 759 dagnode.Links[i] = &dag.Link{ 760 Name: link.Name, 761 Size: link.Size, 762 Hash: hash, 763 } 764 } 765 766 return dagnode, nil 767 }