@cryptotaxi247 / kubo / commits / c542cb52a

core/commands2: Added 'object' command

Matt Bell committed Nov 8, 2014 at 19:56 UTC c542cb52aa082d2e346979e02c30996accfb7d93
3 files changed +313 -6
core/commands2/add.go
+15 -6
@@ -97,12 +97,7 @@ func add(n *core.IpfsNode, readers []io.Reader) ([]*dag.Node, error) {
97 return nil, err
98 }
99
100 - err = n.DAG.AddRecursive(node) // add the file to the graph + local storage
101 - if err != nil {
102 - return nil, err
103 - }
104 -
105 - err = n.Pinning.Pin(node, true) // ensure we keep it
100 + err = addNode(n, node)
101 if err != nil {
102 return nil, err
103 }
@@ -111,3 +106,17 @@ func add(n *core.IpfsNode, readers []io.Reader) ([]*dag.Node, error) {
106 }
107 return dagnodes, nil
108 }
109 +
110 +func addNode(n *core.IpfsNode, node *dag.Node) error {
111 + err := n.DAG.AddRecursive(node) // add the file to the graph + local storage
112 + if err != nil {
113 + return err
114 + }
115 +
116 + err = n.Pinning.Pin(node, true) // ensure we keep it
117 + if err != nil {
118 + return err
119 + }
120 +
121 + return nil
122 +}
core/commands2/object.go new
+297
@@ -0,0 +1,297 @@
1 +package commands
2 +
3 +import (
4 + "bytes"
5 + "encoding/json"
6 + "errors"
7 + "io"
8 + "io/ioutil"
9 +
10 + cmds "github.com/jbenet/go-ipfs/commands"
11 + "github.com/jbenet/go-ipfs/core"
12 + dag "github.com/jbenet/go-ipfs/merkledag"
13 +)
14 +
15 +// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
16 +var ErrObjectTooLarge = errors.New("input object was too large. limit is 512kbytes")
17 +
18 +const inputLimit = 512 * 1024
19 +
20 +var objectCmd = &cmds.Command{
21 + Description: "Interact with ipfs objects",
22 + Help: `'ipfs object' is a plumbing command used to manipulate DAG objects directly.`,
23 +
24 + Subcommands: map[string]*cmds.Command{
25 + "data": objectDataCmd,
26 + "links": objectLinksCmd,
27 + "get": objectGetCmd,
28 + "put": objectPutCmd,
29 + },
30 +}
31 +
32 +var objectDataCmd = &cmds.Command{
33 + Description: "Outputs the raw bytes in an IPFS object",
34 + Help: `ipfs data is a plumbing command for retreiving the raw bytes stored in a DAG node.
35 +It outputs to stdout, and <key> is a base58 encoded multihash.
36 +
37 +Note that the "--encoding" option does not affect the output, since the
38 +output is the raw data of the object.
39 +`,
40 +
41 + Arguments: []cmds.Argument{
42 + cmds.Argument{"key", cmds.ArgString, true, false,
43 + "Key of the object to retrieve, in base58-encoded multihash format"},
44 + },
45 + Run: func(res cmds.Response, req cmds.Request) {
46 + n := req.Context().Node
47 +
48 + key, ok := req.Arguments()[0].(string)
49 + if !ok {
50 + res.SetError(errors.New("cast error"), cmds.ErrNormal)
51 + return
52 + }
53 +
54 + reader, err := objectData(n, key)
55 + if err != nil {
56 + res.SetError(err, cmds.ErrNormal)
57 + return
58 + }
59 +
60 + res.SetOutput(reader)
61 + },
62 +}
63 +
64 +var objectLinksCmd = &cmds.Command{
65 + Description: "Outputs the links pointed to by the specified object",
66 + Help: `'ipfs block get' is a plumbing command for retreiving raw IPFS blocks.
67 +It outputs to stdout, and <key> is a base58 encoded multihash.`,
68 +
69 + Arguments: []cmds.Argument{
70 + cmds.Argument{"key", cmds.ArgString, true, false,
71 + "Key of the object to retrieve, in base58-encoded multihash format"},
72 + },
73 + Run: func(res cmds.Response, req cmds.Request) {
74 + n := req.Context().Node
75 +
76 + key, ok := req.Arguments()[0].(string)
77 + if !ok {
78 + res.SetError(errors.New("cast error"), cmds.ErrNormal)
79 + return
80 + }
81 +
82 + output, err := objectLinks(n, key)
83 + if err != nil {
84 + res.SetError(err, cmds.ErrNormal)
85 + return
86 + }
87 +
88 + res.SetOutput(output)
89 + },
90 + Type: &Object{},
91 +}
92 +
93 +var objectGetCmd = &cmds.Command{
94 + Description: "Get and serialize the DAG node named by <key>",
95 + Help: `'ipfs object get' is a plumbing command for retreiving DAG nodes.
96 +It serializes the DAG node to the format specified by the "--encoding" flag.
97 +It outputs to stdout, and <key> is a base58 encoded multihash.
98 +
99 +This command outputs data in the following encodings: "protobuf", "json", "xml"
100 +(Specified by the "--encoding" flag)`,
101 +
102 + Arguments: []cmds.Argument{
103 + cmds.Argument{"key", cmds.ArgString, true, false,
104 + "Key of the object to retrieve, in base58-encoded multihash format"},
105 + },
106 + Run: func(res cmds.Response, req cmds.Request) {
107 + n := req.Context().Node
108 +
109 + key, ok := req.Arguments()[0].(string)
110 + if !ok {
111 + res.SetError(errors.New("cast error"), cmds.ErrNormal)
112 + return
113 + }
114 +
115 + object, err := objectGet(n, key)
116 + if err != nil {
117 + res.SetError(err, cmds.ErrNormal)
118 + return
119 + }
120 +
121 + res.SetOutput(object)
122 + },
123 + Type: &dag.Node{},
124 + Marshallers: map[cmds.EncodingType]cmds.Marshaller{
125 + cmds.EncodingType("protobuf"): func(res cmds.Response) ([]byte, error) {
126 + object := res.Output().(*dag.Node)
127 + return object.Marshal()
128 + },
129 + },
130 +}
131 +
132 +var objectPutCmd = &cmds.Command{
133 + Description: "Stores input as a DAG object, outputs its key",
134 + Help: `'ipfs object put' is a plumbing command for storing DAG nodes.
135 +It reads from stdin, and the output is a base58 encoded multihash.
136 +
137 +Data should be in the format specified by <encoding>.
138 +<encoding> may be one of the following:
139 + * "protobuf"
140 + * "json"
141 +`,
142 +
143 + Arguments: []cmds.Argument{
144 + cmds.Argument{"data", cmds.ArgFile, true, false,
145 + "Data to be stored as a DAG object, encoded as specified in <encoding>"},
146 + cmds.Argument{"encoding", cmds.ArgString, true, false,
147 + "Encoding type of <data>, either \"protobuf\" or \"json\""},
148 + },
149 + Run: func(res cmds.Response, req cmds.Request) {
150 + n := req.Context().Node
151 +
152 + input, ok := req.Arguments()[0].(io.Reader)
153 + if !ok {
154 + res.SetError(errors.New("cast error"), cmds.ErrNormal)
155 + return
156 + }
157 +
158 + encoding, ok := req.Arguments()[1].(string)
159 + if !ok {
160 + res.SetError(errors.New("cast error"), cmds.ErrNormal)
161 + return
162 + }
163 +
164 + output, err := objectPut(n, input, encoding)
165 + if err != nil {
166 + errType := cmds.ErrNormal
167 + if err == ErrUnknownObjectEnc {
168 + errType = cmds.ErrClient
169 + }
170 + res.SetError(err, errType)
171 + return
172 + }
173 +
174 + res.SetOutput(output)
175 + },
176 + Type: &Object{},
177 +}
178 +
179 +// objectData takes a key string and writes out the raw bytes of that node (if there is one)
180 +func objectData(n *core.IpfsNode, key string) (io.Reader, error) {
181 + dagnode, err := n.Resolver.ResolvePath(key)
182 + if err != nil {
183 + return nil, err
184 + }
185 +
186 + log.Debugf("objectData: found dagnode %q (# of bytes: %d - # links: %d)", key, len(dagnode.Data), len(dagnode.Links))
187 +
188 + return bytes.NewReader(dagnode.Data), nil
189 +}
190 +
191 +// objectLinks takes a key string and lists the links it points to
192 +func objectLinks(n *core.IpfsNode, key string) (*Object, error) {
193 + dagnode, err := n.Resolver.ResolvePath(key)
194 + if err != nil {
195 + return nil, err
196 + }
197 +
198 + log.Debugf("objectLinks: found dagnode %q (# of bytes: %d - # links: %d)", key, len(dagnode.Data), len(dagnode.Links))
199 +
200 + return getOutput(dagnode)
201 +}
202 +
203 +// objectGet takes a key string from args and a format option and serializes the dagnode to that format
204 +func objectGet(n *core.IpfsNode, key string) (*dag.Node, error) {
205 + dagnode, err := n.Resolver.ResolvePath(key)
206 + if err != nil {
207 + return nil, err
208 + }
209 +
210 + log.Debugf("objectGet: found dagnode %q (# of bytes: %d - # links: %d)", key, len(dagnode.Data), len(dagnode.Links))
211 +
212 + return dagnode, nil
213 +}
214 +
215 +// objectPut takes a format option, serializes bytes from stdin and updates the dag with that data
216 +func objectPut(n *core.IpfsNode, input io.Reader, encoding string) (*Object, error) {
217 + var (
218 + dagnode *dag.Node
219 + data []byte
220 + err error
221 + )
222 +
223 + data, err = ioutil.ReadAll(io.LimitReader(input, inputLimit+10))
224 + if err != nil {
225 + return nil, err
226 + }
227 +
228 + if len(data) >= inputLimit {
229 + return nil, ErrObjectTooLarge
230 + }
231 +
232 + switch getObjectEnc(encoding) {
233 + case objectEncodingJSON:
234 + dagnode = new(dag.Node)
235 + err = json.Unmarshal(data, dagnode)
236 +
237 + case objectEncodingProtobuf:
238 + dagnode, err = dag.Decoded(data)
239 +
240 + default:
241 + return nil, ErrUnknownObjectEnc
242 + }
243 +
244 + if err != nil {
245 + return nil, err
246 + }
247 +
248 + err = addNode(n, dagnode)
249 + if err != nil {
250 + return nil, err
251 + }
252 +
253 + return getOutput(dagnode)
254 +}
255 +
256 +// ErrUnknownObjectEnc is returned if a invalid encoding is supplied
257 +var ErrUnknownObjectEnc = errors.New("unknown object encoding")
258 +
259 +type objectEncoding string
260 +
261 +const (
262 + objectEncodingJSON objectEncoding = "json"
263 + objectEncodingProtobuf = "protobuf"
264 +)
265 +
266 +func getObjectEnc(o interface{}) objectEncoding {
267 + v, ok := o.(string)
268 + if !ok {
269 + // chosen as default because it's human readable
270 + log.Warning("option is not a string - falling back to json")
271 + return objectEncodingJSON
272 + }
273 +
274 + return objectEncoding(v)
275 +}
276 +
277 +func getOutput(dagnode *dag.Node) (*Object, error) {
278 + key, err := dagnode.Key()
279 + if err != nil {
280 + return nil, err
281 + }
282 +
283 + output := &Object{
284 + Hash: key.Pretty(),
285 + Links: make([]Link, len(dagnode.Links)),
286 + }
287 +
288 + for i, link := range dagnode.Links {
289 + output.Links[i] = Link{
290 + Name: link.Name,
291 + Hash: link.Hash.B58String(),
292 + Size: link.Size,
293 + }
294 + }
295 +
296 + return output, nil
297 +}
core/commands2/root.go
+1
@@ -70,6 +70,7 @@ var rootSubcommands = map[string]*cmds.Command{
70 "mount": mountCmd,
71 "block": blockCmd,
72 "update": updateCmd,
73 + "object": objectCmd,
74 }
75
76 func init() {