1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-09-10 05:52:20 +08:00

refactor object patch command to work more betterer

License: MIT
Signed-off-by: Jeromy <jeromyj@gmail.com>
This commit is contained in:
Jeromy
2016-01-03 11:34:57 -08:00
parent 9aea2c7895
commit d4ffc9454f
4 changed files with 330 additions and 254 deletions

View File

@ -1,4 +1,4 @@
package commands package objectcmd
import ( import (
"bytes" "bytes"
@ -13,14 +13,11 @@ import (
mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" mh "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
key "github.com/ipfs/go-ipfs/blocks/key"
cmds "github.com/ipfs/go-ipfs/commands" cmds "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core" core "github.com/ipfs/go-ipfs/core"
dag "github.com/ipfs/go-ipfs/merkledag" dag "github.com/ipfs/go-ipfs/merkledag"
dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
path "github.com/ipfs/go-ipfs/path" path "github.com/ipfs/go-ipfs/path"
ft "github.com/ipfs/go-ipfs/unixfs" ft "github.com/ipfs/go-ipfs/unixfs"
u "github.com/ipfs/go-ipfs/util"
) )
// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k // ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
@ -61,17 +58,17 @@ ipfs object patch <args> - Create new object from old ones
}, },
Subcommands: map[string]*cmds.Command{ Subcommands: map[string]*cmds.Command{
"data": objectDataCmd, "data": ObjectDataCmd,
"links": objectLinksCmd, "links": ObjectLinksCmd,
"get": objectGetCmd, "get": ObjectGetCmd,
"put": objectPutCmd, "put": ObjectPutCmd,
"stat": objectStatCmd, "stat": ObjectStatCmd,
"new": objectNewCmd, "new": ObjectNewCmd,
"patch": objectPatchCmd, "patch": ObjectPatchCmd,
}, },
} }
var objectDataCmd = &cmds.Command{ var ObjectDataCmd = &cmds.Command{
Helptext: cmds.HelpText{ Helptext: cmds.HelpText{
Tagline: "Outputs the raw bytes in an IPFS object", Tagline: "Outputs the raw bytes in an IPFS object",
ShortDescription: ` ShortDescription: `
@ -109,7 +106,7 @@ output is the raw data of the object.
}, },
} }
var objectLinksCmd = &cmds.Command{ var ObjectLinksCmd = &cmds.Command{
Helptext: cmds.HelpText{ Helptext: cmds.HelpText{
Tagline: "Outputs the links pointed to by the specified object", Tagline: "Outputs the links pointed to by the specified object",
ShortDescription: ` ShortDescription: `
@ -158,7 +155,7 @@ multihash.
Type: Object{}, Type: Object{},
} }
var objectGetCmd = &cmds.Command{ var ObjectGetCmd = &cmds.Command{
Helptext: cmds.HelpText{ Helptext: cmds.HelpText{
Tagline: "Get and serialize the DAG node named by <key>", Tagline: "Get and serialize the DAG node named by <key>",
ShortDescription: ` ShortDescription: `
@ -229,7 +226,7 @@ This command outputs data in the following encodings:
}, },
} }
var objectStatCmd = &cmds.Command{ var ObjectStatCmd = &cmds.Command{
Helptext: cmds.HelpText{ Helptext: cmds.HelpText{
Tagline: "Get stats for the DAG node named by <key>", Tagline: "Get stats for the DAG node named by <key>",
ShortDescription: ` ShortDescription: `
@ -290,7 +287,7 @@ var objectStatCmd = &cmds.Command{
}, },
} }
var objectPutCmd = &cmds.Command{ var ObjectPutCmd = &cmds.Command{
Helptext: cmds.HelpText{ Helptext: cmds.HelpText{
Tagline: "Stores input as a DAG object, outputs its key", Tagline: "Stores input as a DAG object, outputs its key",
ShortDescription: ` ShortDescription: `
@ -377,7 +374,7 @@ and then run
Type: Object{}, Type: Object{},
} }
var objectNewCmd = &cmds.Command{ var ObjectNewCmd = &cmds.Command{
Helptext: cmds.HelpText{ Helptext: cmds.HelpText{
Tagline: "creates a new object from an ipfs template", Tagline: "creates a new object from an ipfs template",
ShortDescription: ` ShortDescription: `
@ -430,235 +427,6 @@ Available templates:
Type: Object{}, Type: Object{},
} }
var objectPatchCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create a new merkledag object based on an existing one",
ShortDescription: `
'ipfs object patch <root> <cmd> <args>' is a plumbing command used to
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. 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
Examples:
EMPTY_DIR=$(ipfs object new unixfs-dir)
BAR=$(echo "bar" | ipfs add -q)
ipfs object patch $EMPTY_DIR add-link foo $BAR
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.
ipfs object patch $FOO_BAR rm-link foo
This removes the link named foo from the hash in $FOO_BAR and returns the
resulting object hash.
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
`,
},
Options: []cmds.Option{
cmds.BoolOption("create", "p", "create intermediate directories on add-link"),
},
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(),
},
Type: Object{},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
rootarg := req.Arguments()[0]
if strings.HasPrefix(rootarg, "/ipfs/") {
rootarg = rootarg[6:]
}
rhash := key.B58KeyDecode(rootarg)
if rhash == "" {
res.SetError(fmt.Errorf("incorrectly formatted root hash: %s", req.Arguments()[0]), cmds.ErrNormal)
return
}
rnode, err := nd.DAG.Get(req.Context(), rhash)
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
}
res.SetOutput(&Object{Hash: k.B58String()})
case "rm-link":
k, err := rmLinkCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: k.B58String()})
case "set-data":
k, err := setDataCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: k.B58String()})
case "append-data":
k, err := appendDataCaller(req, rnode)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: k.B58String()})
default:
res.SetError(fmt.Errorf("unrecognized subcommand"), cmds.ErrNormal)
return
}
},
Marshalers: cmds.MarshalerMap{
cmds.Text: func(res cmds.Response) (io.Reader, error) {
o, ok := res.Output().(*Object)
if !ok {
return nil, u.ErrCast()
}
return strings.NewReader(o.Hash + "\n"), nil
},
},
}
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.InvocContext().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.InvocContext().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.InvocContext().GetNode()
if err != nil {
return "", err
}
path := req.Arguments()[2]
e := dagutils.NewDagEditor(root, nd.DAG)
err = e.RmLink(req.Context(), path)
if err != nil {
return "", err
}
nnode, err := e.Finalize(nd.DAG)
if err != nil {
return "", err
}
return nnode.Key()
}
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.InvocContext().GetNode()
if err != nil {
return "", err
}
path := req.Arguments()[2]
childk := key.B58KeyDecode(req.Arguments()[3])
create, _, err := req.Option("create").Bool()
if err != nil {
return "", err
}
var createfunc func() *dag.Node
if create {
createfunc = func() *dag.Node {
return &dag.Node{Data: ft.FolderPBData()}
}
}
e := dagutils.NewDagEditor(root, nd.DAG)
childnd, err := nd.DAG.Get(req.Context(), childk)
if err != nil {
return "", err
}
err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc)
if err != nil {
return "", err
}
nnode, err := e.Finalize(nd.DAG)
if err != nil {
return "", err
}
return nnode.Key()
}
func nodeFromTemplate(template string) (*dag.Node, error) { func nodeFromTemplate(template string) (*dag.Node, error) {
switch template { switch template {
case "unixfs-dir": case "unixfs-dir":
@ -757,7 +525,6 @@ func getObjectEnc(o interface{}) objectEncoding {
v, ok := o.(string) v, ok := o.(string)
if !ok { if !ok {
// chosen as default because it's human readable // chosen as default because it's human readable
log.Warning("option is not a string - falling back to json")
return objectEncodingJSON return objectEncodingJSON
} }

View File

@ -0,0 +1,308 @@
package objectcmd
import (
"io"
"io/ioutil"
"strings"
key "github.com/ipfs/go-ipfs/blocks/key"
cmds "github.com/ipfs/go-ipfs/commands"
core "github.com/ipfs/go-ipfs/core"
dag "github.com/ipfs/go-ipfs/merkledag"
dagutils "github.com/ipfs/go-ipfs/merkledag/utils"
path "github.com/ipfs/go-ipfs/path"
ft "github.com/ipfs/go-ipfs/unixfs"
u "github.com/ipfs/go-ipfs/util"
)
var ObjectPatchCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Create a new merkledag object based on an existing one",
ShortDescription: `
'ipfs object patch <root> <cmd> <args>' is a plumbing command used to
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. 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
ipfs object patch $FOO_BAR rm-link foo
This removes the link named foo from the hash in $FOO_BAR and returns the
resulting object hash.
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
`,
},
Arguments: []cmds.Argument{},
Subcommands: map[string]*cmds.Command{
"append-data": patchAppendDataCmd,
"add-link": patchAddLinkCmd,
"rm-link": patchRmLinkCmd,
"set-data": patchSetDataCmd,
},
}
func objectMarshaler(res cmds.Response) (io.Reader, error) {
o, ok := res.Output().(*Object)
if !ok {
return nil, u.ErrCast()
}
return strings.NewReader(o.Hash + "\n"), nil
}
var patchAppendDataCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Append data to the data segment of a dag node",
ShortDescription: `
`,
},
Arguments: []cmds.Argument{
cmds.StringArg("root", true, false, "the hash of the node to modify"),
cmds.FileArg("data", true, false, "data to append").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
root, err := path.ParsePath(req.Arguments()[0])
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
rootnd, err := core.Resolve(req.Context(), nd, root)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
data, err := ioutil.ReadAll(req.Files())
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
rootnd.Data = append(rootnd.Data, data...)
newkey, err := nd.DAG.Add(rootnd)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: newkey.B58String()})
},
Type: Object{},
Marshalers: cmds.MarshalerMap{
cmds.Text: objectMarshaler,
},
}
var patchSetDataCmd = &cmds.Command{
Helptext: cmds.HelpText{},
Arguments: []cmds.Argument{
cmds.StringArg("root", true, false, "the hash of the node to modify"),
cmds.FileArg("data", true, false, "data fill with").EnableStdin(),
},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
rp, err := path.ParsePath(req.Arguments()[0])
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
root, err := core.Resolve(req.Context(), nd, rp)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
data, err := ioutil.ReadAll(req.Files())
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
root.Data = data
newkey, err := nd.DAG.Add(root)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: newkey.B58String()})
},
Type: Object{},
Marshalers: cmds.MarshalerMap{
cmds.Text: objectMarshaler,
},
}
var patchRmLinkCmd = &cmds.Command{
Helptext: cmds.HelpText{},
Arguments: []cmds.Argument{
cmds.StringArg("root", true, false, "the hash of the node to modify"),
cmds.StringArg("link", true, false, "name of the link to remove"),
},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
rootp, err := path.ParsePath(req.Arguments()[0])
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
root, err := core.Resolve(req.Context(), nd, rootp)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
path := req.Arguments()[1]
e := dagutils.NewDagEditor(root, nd.DAG)
err = e.RmLink(req.Context(), path)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
nnode, err := e.Finalize(nd.DAG)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
nk, err := nnode.Key()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: nk.B58String()})
},
Type: Object{},
Marshalers: cmds.MarshalerMap{
cmds.Text: objectMarshaler,
},
}
var patchAddLinkCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "add a link to a given object",
ShortDescription: `
Examples:
EMPTY_DIR=$(ipfs object new unixfs-dir)
BAR=$(echo "bar" | ipfs add -q)
ipfs object patch $EMPTY_DIR add-link foo $BAR
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.
`,
},
Options: []cmds.Option{
cmds.BoolOption("p", "create", "create intermediary nodes"),
},
Arguments: []cmds.Argument{
cmds.StringArg("root", true, false, "the hash of the node to modify"),
cmds.StringArg("name", true, false, "name of link to create"),
cmds.StringArg("ref", true, false, "ipfs object to add link to"),
},
Run: func(req cmds.Request, res cmds.Response) {
nd, err := req.InvocContext().GetNode()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
rootp, err := path.ParsePath(req.Arguments()[0])
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
root, err := core.Resolve(req.Context(), nd, rootp)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
path := req.Arguments()[1]
childk := key.B58KeyDecode(req.Arguments()[2])
create, _, err := req.Option("create").Bool()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
var createfunc func() *dag.Node
if create {
createfunc = func() *dag.Node {
return &dag.Node{Data: ft.FolderPBData()}
}
}
e := dagutils.NewDagEditor(root, nd.DAG)
childnd, err := nd.DAG.Get(req.Context(), childk)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
nnode, err := e.Finalize(nd.DAG)
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
nk, err := nnode.Key()
if err != nil {
res.SetError(err, cmds.ErrNormal)
return
}
res.SetOutput(&Object{Hash: nk.B58String()})
},
Type: Object{},
Marshalers: cmds.MarshalerMap{
cmds.Text: objectMarshaler,
},
}

View File

@ -6,6 +6,7 @@ import (
cmds "github.com/ipfs/go-ipfs/commands" cmds "github.com/ipfs/go-ipfs/commands"
files "github.com/ipfs/go-ipfs/core/commands/files" files "github.com/ipfs/go-ipfs/core/commands/files"
ocmd "github.com/ipfs/go-ipfs/core/commands/object"
unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs" unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs"
logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log" logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log"
) )
@ -107,7 +108,7 @@ var rootSubcommands = map[string]*cmds.Command{
"ls": LsCmd, "ls": LsCmd,
"mount": MountCmd, "mount": MountCmd,
"name": NameCmd, "name": NameCmd,
"object": ObjectCmd, "object": ocmd.ObjectCmd,
"pin": PinCmd, "pin": PinCmd,
"ping": PingCmd, "ping": PingCmd,
"refs": RefsCmd, "refs": RefsCmd,
@ -148,11 +149,11 @@ var rootROSubcommands = map[string]*cmds.Command{
}, },
"object": &cmds.Command{ "object": &cmds.Command{
Subcommands: map[string]*cmds.Command{ Subcommands: map[string]*cmds.Command{
"data": objectDataCmd, "data": ocmd.ObjectDataCmd,
"links": objectLinksCmd, "links": ocmd.ObjectLinksCmd,
"get": objectGetCmd, "get": ocmd.ObjectGetCmd,
"stat": objectStatCmd, "stat": ocmd.ObjectStatCmd,
"patch": objectPatchCmd, "patch": ocmd.ObjectPatchCmd,
}, },
}, },
"refs": RefsROCmd, "refs": RefsROCmd,

View File

@ -16,7 +16,7 @@ test_patch_create_path() {
target=$3 target=$3
test_expect_success "object patch --create works" ' test_expect_success "object patch --create works" '
PCOUT=$(ipfs object patch --create $root add-link $name $target) PCOUT=$(ipfs object patch $root add-link --create $name $target)
' '
test_expect_success "output looks good" ' test_expect_success "output looks good" '