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() {