Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

object plumbing commands #185

Merged
merged 4 commits into from
Oct 19, 2014
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/ipfs/ipfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Tool commands:
version Show ipfs version information.
commands List all available commands.

Plumbing commands:
block Interact with raw blocks in the datastore
object Interact with raw dag nodes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 on this section. maybe move it below "advanced" as things like mount will be used commonly.

Advanced Commands:

mount Mount an ipfs read-only mountpoint.
Expand All @@ -63,6 +67,7 @@ Use "ipfs help <command>" for more information about a command.
cmdIpfsBootstrap,
cmdIpfsDiag,
cmdIpfsBlock,
cmdIpfsObject,
cmdIpfsLog,
},
Flag: *flag.NewFlagSet("ipfs", flag.ExitOnError),
Expand Down
112 changes: 112 additions & 0 deletions cmd/ipfs/objects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
flag "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gonuts/flag"
commander "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/commander"
"github.com/jbenet/go-ipfs/core/commands"
)

var cmdIpfsObject = &commander.Command{
UsageLine: "object",
Short: "interact with ipfs objects",
Long: `ipfs object - interact with ipfs objects

ipfs object data <key> - return the data for this key as raw bytes
ipfs object links <key> - lists (the keys of ?) the links this key points to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe:
output the object's data (raw bytes)
output the object's link table

ipfs object get <key> - output dag object to stdout
ipfs object put - add dag object from stdin

ipfs object is a plumbing command used to manipulate dag objects directly.
- <key> is a base58 encoded multihash.
- It reads from stdin or writes to stdout.
- It accepts multiple encodings: --encoding=[ protobuf, json, ... ]`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there are any other encodings we would want to support...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depends on how nice we want to be. msgpack, xml, rdf, and so on do see some use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, xml encoding might be nice to integrate with older systems

Subcommands: []*commander.Command{
cmdIpfsObjectData,
cmdIpfsObjectLinks,
cmdIpfsObjectGet,
cmdIpfsObjectPut,
},
Flag: *flag.NewFlagSet("ipfs-object", flag.ExitOnError),
}

var cmdIpfsObjectData = &commander.Command{
UsageLine: "data <key>",
Short: "data outputs the raw bytes named by <key>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make it more clear that its stripping the actual "Data" out of the protobuf? not sure how to word that

Long: `ipfs data <key> - data outputs the raw bytes named by <key>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command hierarchy: ipfs object data <key>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, if you want, we can merge this and i can clean up all the wording -- sorry i'm very picky :s

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine by me :)


ipfs data is a plumbing command for retreiving the raw bytes stored in a dag node.
It outputs to stdout, and <key> is a base58 encoded multihash.`,
Run: makeCommand(command{
name: "objectData",
args: 1,
flags: nil,
online: true,
cmdFn: commands.ObjectData,
}),
}

var cmdIpfsObjectLinks = &commander.Command{
UsageLine: "links <key>",
Short: "outputs the links pointed to by <key>",
Long: `ipfs links <key> - outputs the links pointed to by <key>

ipfs block get is a plumbing command for retreiving raw ipfs blocks.
It outputs to stdout, and <key> is a base58 encoded multihash.`,
Run: makeCommand(command{
name: "objectLinks",
args: 1,
flags: nil,
online: true,
cmdFn: commands.ObjectLinks,
}),
}

func init() {
cmdIpfsObjectGet.Flag.String("encoding", "json", "the encoding to use..")
cmdIpfsObjectPut.Flag.String("encoding", "json", "the encoding to use..")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im curious what some example usages of this command with the encodings are, especially for put

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it was supposed for get as json > edit > put as json

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you would be able to put an object described as json, as below, and have it be turned into the native encoding (protobuf) and so on for you.

{
  "links": [
    ["QmbnJp7DAnncbvQEpvPWcCErsoCBHaSn3fM8wS9V7Ty9wR", "149759", "accum-transform"],
    ["QmaksLKRUazZNJzqJkcZoz4dk6uJqVEJbDCUGXgUS88DT3", "1988708", "request"],
    ["QmVA4mvPhsq5C48XBwHB5VE3MLcUSKXyYpmpxxovKDMWGR", "125726", "through2"]
  ],
  "data": "some data"
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

being able to manipulate json and put it back will be a big deal. we need to make sure that all the tooling we have exposes human readable versions easily. (analogous to the power of "view source" on the web).

When it comes to adoption, human readability has often trumped performance. Why not both.

}

var cmdIpfsObjectGet = &commander.Command{
UsageLine: "get <key>",
Short: "get and serialize the dag node named by <key>",
Long: `ipfs get <key> - get and output the dag node named by <key>

ipfs object get is a plumbing command for retreiving dag nodes.
It serialize the dag node to the format specified by the format flag.
It outputs to stdout, and <key> is a base58 encoded multihash.

Formats:

This command outputs and accepts data in a variety of encodings: protobuf, json, etc.
Use the --encoding flag
`,
Run: makeCommand(command{
name: "blockGet",
args: 1,
flags: []string{"encoding"},
online: true,
cmdFn: commands.ObjectGet,
}),
}

var cmdIpfsObjectPut = &commander.Command{
UsageLine: "put",
Short: "store stdin as a dag object, outputs <key>",
Long: `ipfs put - store stdin as a dag object, outputs <key>

ipfs object put is a plumbing command for storing dag nodes.
It serialize the dag node to the format specified by the format flag.
It reads from stding, and <key> is a base58 encoded multihash.

Formats:

This command outputs and accepts data in a variety of encodings: protobuf, json, etc.
Use the --encoding flag`,
Run: makeCommand(command{
name: "blockPut",
args: 0,
flags: []string{"encoding"},
online: true,
cmdFn: commands.ObjectPut,
}),
}
131 changes: 131 additions & 0 deletions core/commands/object.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package commands

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/jbenet/go-ipfs/core"
dag "github.com/jbenet/go-ipfs/merkledag"
)

// ObjectData takes a key string from args and writes out the raw bytes of that node (if there is one)
func ObjectData(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
dagnode, err := n.Resolver.ResolvePath(args[0])
if err != nil {
return fmt.Errorf("objectData error: %v", err)
}
log.Debug("objectData: found dagnode %q (# of bytes: %d - # links: %d)", args[0], len(dagnode.Data), len(dagnode.Links))

_, err = io.Copy(out, bytes.NewReader(dagnode.Data))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out.Write(dagnode.Data)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't you have to check the number of bytes written for larger files?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also: io.Copy() checks for implementations of WriteTo or ReadFrom which saves copying in ram

return err
}

// ObjectLinks takes a key string from args and lists the links it points to
func ObjectLinks(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
dagnode, err := n.Resolver.ResolvePath(args[0])
if err != nil {
return fmt.Errorf("objectLinks error: %v", err)
}
log.Debug("ObjectLinks: found dagnode %q (# of bytes: %d - # links: %d)", args[0], len(dagnode.Data), len(dagnode.Links))

if len(dagnode.Links) == 0 {
// TODO(cryptix): named error?
return errors.New("objectLinks: no links in this node")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 named errors.

But-- in this case, empty links is not an error. it's just empty. like:

touch foo
cat foo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the root file object can(should) also contain data (ive been trying to push for this to improve latencies)

}

for _, link := range dagnode.Links {
_, err = fmt.Fprintf(out, "%s %d %q\n", link.Hash.B58String(), link.Size, link.Name)
if err != nil {
break
}
}

return err
}

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

return objectEncoding(v)
}

// ObjectGet takes a key string from args and a format option and serializes the dagnode to that format
func ObjectGet(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
dagnode, err := n.Resolver.ResolvePath(args[0])
if err != nil {
return fmt.Errorf("ObjectGet error: %v", err)
}
log.Debug("objectGet: found dagnode %q (# of bytes: %d - # links: %d)", args[0], len(dagnode.Data), len(dagnode.Links))

// sadly all encodings dont implement a common interface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should do that. let's make a codec.Codec interface.

(and, this reminds me, we'll need a multicodec format if we want to enable multiple native encodings)

var data []byte
switch getObjectEnc(opts["encoding"]) {
case objectEncodingJSON:
data, err = json.MarshalIndent(dagnode, "", " ")

case objectEncodingProtobuf:
data, err = dagnode.Marshal()

default:
return ErrUnknownObjectEnc
}

if err != nil {
return fmt.Errorf("ObjectGet error: %v", err)
}

_, err = io.Copy(out, bytes.NewReader(data))
return err
}

// ObjectPut takes a format option, serilizes bytes from stdin and updates the dag with that data
func ObjectPut(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
var (
dagnode *dag.Node
data []byte
err error
)

data, err = ioutil.ReadAll(os.Stdin)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be useful to break on a maximum size. not sure good an upper bound is.
1MB? 512K? cc @whyrusleeping

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive been keeping the number 512k in my head as a max block size. I have no scientific evidence to back up that claim though.

if err != nil {
return fmt.Errorf("ObjectPut error: %v", err)
}

switch getObjectEnc(opts["encoding"]) {
case objectEncodingJSON:
dagnode = new(dag.Node)
err = json.Unmarshal(data, dagnode)

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

default:
return ErrUnknownObjectEnc
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah let's do that coding.Codec thing.


if err != nil {
return fmt.Errorf("ObjectPut error: %v", err)
}

return addNode(n, dagnode, "stdin", out)
}