@cryptotaxi247 / kubo / commits / 40e423d6d

implement an ipfs patch command for modifying merkledag objects

WIP: object creator command better docs move patch command into object namespace dont ignore cancel funcs addressing comment from CR add two new subcommands to object patch and clean up main Run func cancel contexts in early returns switch to util.Key

Jeromy committed May 28, 2015 at 13:53 UTC 40e423d6dcae8dd6fa3edbe3d639c0ddce9752a3
2 files changed +284 -5
core/commands/object.go
+282 -5
@@ -9,13 +9,18 @@ import (
9 "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 cmds "github.com/ipfs/go-ipfs/commands"
19 core "github.com/ipfs/go-ipfs/core"
20 dag "github.com/ipfs/go-ipfs/merkledag"
21 path "github.com/ipfs/go-ipfs/path"
22 + ft "github.com/ipfs/go-ipfs/unixfs"
23 + u "github.com/ipfs/go-ipfs/util"
24 )
25
26 // ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
@@ -45,11 +50,13 @@ var ObjectCmd = &cmds.Command{
50 'ipfs object' is a plumbing command used to manipulate DAG objects
51 directly.`,
52 Synopsis: `
48 -ipfs object get <key> - Get the DAG node named by <key>
49 -ipfs object put <data> - Stores input, outputs its key
50 -ipfs object data <key> - Outputs raw bytes in an object
51 -ipfs object links <key> - Outputs links pointed to by object
52 -ipfs object stat <key> - Outputs statistics of object
53 +ipfs object get <key> - Get the DAG node named by <key>
54 +ipfs object put <data> - Stores input, outputs its key
55 +ipfs object data <key> - Outputs raw bytes in an object
56 +ipfs object links <key> - Outputs links pointed to by object
57 +ipfs object stat <key> - Outputs statistics of object
58 +ipfs object new <template> - Create new ipfs objects
59 +ipfs object patch <args> - Create new object from old ones
60 `,
61 },
62
@@ -59,6 +66,8 @@ ipfs object stat <key> - Outputs statistics of object
66 "get": objectGetCmd,
67 "put": objectPutCmd,
68 "stat": objectStatCmd,
69 + "new": objectNewCmd,
70 + "patch": objectPatchCmd,
71 },
72 }
73
@@ -348,6 +357,274 @@ Data should be in the format specified by the --inputenc flag.
357 Type: Object{},
358 }
359
360 +var objectNewCmd = &cmds.Command{
361 + Helptext: cmds.HelpText{
362 + Tagline: "creates a new object from an ipfs template",
363 + ShortDescription: `
364 +'ipfs object new' is a plumbing command for creating new DAG nodes.
365 +`,
366 + LongDescription: `
367 +'ipfs object new' is a plumbing command for creating new DAG nodes.
368 +By default it creates and returns a new empty merkledag node, but
369 +you may pass an optional template argument to create a preformatted
370 +node.
371 +`,
372 + },
373 + Arguments: []cmds.Argument{
374 + cmds.StringArg("template", false, false, "optional template to use"),
375 + },
376 + Run: func(req cmds.Request, res cmds.Response) {
377 + n, err := req.Context().GetNode()
378 + if err != nil {
379 + res.SetError(err, cmds.ErrNormal)
380 + return
381 + }
382 +
383 + node := new(dag.Node)
384 + if len(req.Arguments()) == 1 {
385 + template := req.Arguments()[0]
386 + var err error
387 + node, err = nodeFromTemplate(template)
388 + if err != nil {
389 + res.SetError(err, cmds.ErrNormal)
390 + return
391 + }
392 + }
393 +
394 + k, err := n.DAG.Add(node)
395 + if err != nil {
396 + res.SetError(err, cmds.ErrNormal)
397 + return
398 + }
399 + res.SetOutput(&Object{Hash: k.B58String()})
400 + },
401 + Marshalers: cmds.MarshalerMap{
402 + cmds.Text: func(res cmds.Response) (io.Reader, error) {
403 + object := res.Output().(*Object)
404 + return strings.NewReader(object.Hash + "\n"), nil
405 + },
406 + },
407 + Type: Object{},
408 +}
409 +
410 +var objectPatchCmd = &cmds.Command{
411 + Helptext: cmds.HelpText{
412 + Tagline: "Create a new merkledag object based on an existing one",
413 + ShortDescription: `
414 +'ipfs patch <root> [add-link|rm-link] <args>' is a plumbing command used to
415 +build custom DAG objects. It adds and removes links from objects, creating a new
416 +object as a result. This is the merkle-dag version of modifying an object.
417 +
418 +Examples:
419 +
420 + EMPTY_DIR=$(ipfs object new unixfs-dir)
421 + BAR=$(echo "bar" | ipfs add -q)
422 + ipfs patch $EMPTY_DIR add-link foo $BAR
423 +
424 +This takes an empty directory, and adds a link named foo under it, pointing to
425 +a file containing 'bar', and returns the hash of the new object.
426 +
427 + ipfs patch $FOO_BAR rm-link foo
428 +
429 +This removes the link named foo from the hash in $FOO_BAR and returns the
430 +resulting object hash.
431 +`,
432 + },
433 + Options: []cmds.Option{},
434 + Arguments: []cmds.Argument{
435 + cmds.StringArg("root", true, false, "the hash of the node to modify"),
436 + cmds.StringArg("command", true, false, "the operation to perform"),
437 + cmds.StringArg("args", true, true, "extra arguments").EnableStdin(),
438 + },
439 + Type: key.Key(""),
440 + Run: func(req cmds.Request, res cmds.Response) {
441 + nd, err := req.Context().GetNode()
442 + if err != nil {
443 + res.SetError(err, cmds.ErrNormal)
444 + return
445 + }
446 +
447 + rhash := key.B58KeyDecode(req.Arguments()[0])
448 + if rhash == "" {
449 + res.SetError(fmt.Errorf("incorrectly formatted root hash"), cmds.ErrNormal)
450 + return
451 + }
452 +
453 + ctx, cancel := context.WithTimeout(req.Context().Context, time.Second*30)
454 + rnode, err := nd.DAG.Get(ctx, rhash)
455 + if err != nil {
456 + res.SetError(err, cmds.ErrNormal)
457 + cancel()
458 + return
459 + }
460 + cancel()
461 +
462 + action := req.Arguments()[1]
463 +
464 + switch action {
465 + case "add-link":
466 + k, err := addLinkCaller(req, rnode)
467 + if err != nil {
468 + res.SetError(err, cmds.ErrNormal)
469 + return
470 + }
471 + res.SetOutput(k)
472 + case "rm-link":
473 + k, err := rmLinkCaller(req, rnode)
474 + if err != nil {
475 + res.SetError(err, cmds.ErrNormal)
476 + return
477 + }
478 + res.SetOutput(k)
479 + case "set-data":
480 + k, err := setDataCaller(req, rnode)
481 + if err != nil {
482 + res.SetError(err, cmds.ErrNormal)
483 + return
484 + }
485 + res.SetOutput(k)
486 + case "append-data":
487 + k, err := appendDataCaller(req, rnode)
488 + if err != nil {
489 + res.SetError(err, cmds.ErrNormal)
490 + return
491 + }
492 + res.SetOutput(k)
493 + default:
494 + res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
495 + return
496 + }
497 + },
498 + Marshalers: cmds.MarshalerMap{
499 + cmds.Text: func(res cmds.Response) (io.Reader, error) {
500 + k, ok := res.Output().(key.Key)
501 + if !ok {
502 + return nil, u.ErrCast()
503 + }
504 +
505 + return strings.NewReader(k.B58String() + "\n"), nil
506 + },
507 + },
508 +}
509 +
510 +func appendDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
511 + if len(req.Arguments()) < 3 {
512 + return "", fmt.Errorf("not enough arguments for set-data")
513 + }
514 +
515 + nd, err := req.Context().GetNode()
516 + if err != nil {
517 + return "", err
518 + }
519 +
520 + root.Data = append(root.Data, []byte(req.Arguments()[2])...)
521 +
522 + newkey, err := nd.DAG.Add(root)
523 + if err != nil {
524 + return "", err
525 + }
526 +
527 + return newkey, nil
528 +}
529 +
530 +func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
531 + if len(req.Arguments()) < 3 {
532 + return "", fmt.Errorf("not enough arguments for set-data")
533 + }
534 +
535 + nd, err := req.Context().GetNode()
536 + if err != nil {
537 + return "", err
538 + }
539 +
540 + root.Data = []byte(req.Arguments()[2])
541 +
542 + newkey, err := nd.DAG.Add(root)
543 + if err != nil {
544 + return "", err
545 + }
546 +
547 + return newkey, nil
548 +}
549 +
550 +func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
551 + if len(req.Arguments()) < 3 {
552 + return "", fmt.Errorf("not enough arguments for rm-link")
553 + }
554 +
555 + nd, err := req.Context().GetNode()
556 + if err != nil {
557 + return "", err
558 + }
559 +
560 + name := req.Arguments()[2]
561 +
562 + err = root.RemoveNodeLink(name)
563 + if err != nil {
564 + return "", err
565 + }
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 addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) {
576 + if len(req.Arguments()) < 4 {
577 + return "", fmt.Errorf("not enough arguments for add-link")
578 + }
579 +
580 + nd, err := req.Context().GetNode()
581 + if err != nil {
582 + return "", err
583 + }
584 +
585 + name := req.Arguments()[2]
586 + childk := key.B58KeyDecode(req.Arguments()[3])
587 +
588 + newkey, err := addLink(req.Context().Context, nd.DAG, root, name, childk)
589 + if err != nil {
590 + return "", err
591 + }
592 +
593 + return newkey, nil
594 +}
595 +
596 +func addLink(ctx context.Context, ds dag.DAGService, root *dag.Node, childname string, childk key.Key) (key.Key, error) {
597 + ctx, cancel := context.WithTimeout(ctx, time.Second*30)
598 + childnd, err := ds.Get(ctx, childk)
599 + if err != nil {
600 + cancel()
601 + return "", err
602 + }
603 + cancel()
604 +
605 + err = root.AddNodeLinkClean(childname, childnd)
606 + if err != nil {
607 + return "", err
608 + }
609 +
610 + newkey, err := ds.Add(root)
611 + if err != nil {
612 + return "", err
613 + }
614 + return newkey, nil
615 +}
616 +
617 +func nodeFromTemplate(template string) (*dag.Node, error) {
618 + switch template {
619 + case "unixfs-dir":
620 + nd := new(dag.Node)
621 + nd.Data = ft.FolderPBData()
622 + return nd, nil
623 + default:
624 + return nil, fmt.Errorf("template '%s' not found", template)
625 + }
626 +}
627 +
628 // ErrEmptyNode is returned when the input to 'ipfs object put' contains no data
629 var ErrEmptyNode = errors.New("no data or links in this node")
630
routing/record/record.go
+2
@@ -6,11 +6,13 @@ import (
6 proto "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/gogo/protobuf/proto"
7
8 key "github.com/ipfs/go-ipfs/blocks/key"
9 + dag "github.com/ipfs/go-ipfs/merkledag"
10 ci "github.com/ipfs/go-ipfs/p2p/crypto"
11 pb "github.com/ipfs/go-ipfs/routing/dht/pb"
12 eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
13 )
14
15 +var _ = dag.FetchGraph
16 var log = eventlog.Logger("routing/record")
17
18 // MakePutRecord creates and signs a dht record for the given key/value pair