core/commands: Added 'get' command
Matt Bell committed
Jan 22, 2015 at 00:54 UTC
36249b9292633da22dc96f0d955ffa863b57dfff
2 files changed
+179
core/commands/get.go
new
+178
@@ -0,0 +1,178 @@
1
+package commands
2
+
3
+import (
4
+ "archive/tar"
5
+ "bytes"
6
+ "io"
7
+ p "path"
8
+
9
+ cmds "github.com/jbenet/go-ipfs/commands"
10
+ core "github.com/jbenet/go-ipfs/core"
11
+ dag "github.com/jbenet/go-ipfs/merkledag"
12
+ uio "github.com/jbenet/go-ipfs/unixfs/io"
13
+ upb "github.com/jbenet/go-ipfs/unixfs/pb"
14
+
15
+ proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
16
+)
17
+
18
+var GetCmd = &cmds.Command{
19
+ Helptext: cmds.HelpText{
20
+ Tagline: "Download IPFS objects",
21
+ ShortDescription: `
22
+Retrieves the object named by <ipfs-path> and stores the data to disk.
23
+
24
+By default, the output will be stored at ./<ipfs-path>, but an alternate path
25
+can be specified with '--output=<path>' or '-o=<path>'.
26
+
27
+To output a TAR archive instead of unpacked files, use '--archive' or '-a'.
28
+`,
29
+ },
30
+
31
+ Arguments: []cmds.Argument{
32
+ cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to be outputted").EnableStdin(),
33
+ },
34
+ Options: []cmds.Option{
35
+ cmds.StringOption("output", "o", "The path where output should be stored"),
36
+ cmds.BoolOption("archive", "a", "Output a TAR archive"),
37
+ },
38
+ Run: func(req cmds.Request, res cmds.Response) {
39
+ node, err := req.Context().GetNode()
40
+ if err != nil {
41
+ res.SetError(err, cmds.ErrNormal)
42
+ return
43
+ }
44
+
45
+ reader, err := get(node, req.Arguments())
46
+ if err != nil {
47
+ res.SetError(err, cmds.ErrNormal)
48
+ return
49
+ }
50
+ res.SetOutput(reader)
51
+ },
52
+
53
+ // TODO: create a PostRun that splits the archive up into files
54
+}
55
+
56
+func get(node *core.IpfsNode, paths []string) (io.Reader, error) {
57
+ reader := &getReader{signalChan: make(chan struct{})}
58
+ writer := tar.NewWriter(&reader.buf)
59
+
60
+ go func() {
61
+ for _, path := range paths {
62
+ _, err := copyFile(node, writer, path, nil, reader.signalChan)
63
+ if err != nil {
64
+ log.Error(err)
65
+ return
66
+ }
67
+ }
68
+
69
+ err := writer.Flush()
70
+ if err != nil {
71
+ log.Error(err)
72
+ return
73
+ }
74
+
75
+ reader.Close()
76
+ reader.Signal()
77
+ }()
78
+
79
+ return reader, nil
80
+}
81
+
82
+func copyFile(node *core.IpfsNode, writer *tar.Writer, path string, dagnode *dag.Node, signal chan struct{}) (int64, error) {
83
+ var err error
84
+ if dagnode == nil {
85
+ dagnode, err = node.Resolver.ResolvePath(path)
86
+ if err != nil {
87
+ return 0, err
88
+ }
89
+ }
90
+
91
+ pb := new(upb.Data)
92
+ err = proto.Unmarshal(dagnode.Data, pb)
93
+ if err != nil {
94
+ return 0, err
95
+ }
96
+
97
+ written := int64(0)
98
+ if pb.GetType() == upb.Data_Directory {
99
+ err = writer.WriteHeader(&tar.Header{
100
+ Name: path,
101
+ Typeflag: tar.TypeDir,
102
+ Mode: 0777,
103
+ // TODO: set mode, dates, etc. when added to unixFS
104
+ })
105
+ if err != nil {
106
+ return 0, err
107
+ }
108
+
109
+ for _, link := range dagnode.Links {
110
+ n, err := copyFile(node, writer, p.Join(path, link.Name), link.Node, signal)
111
+ if err != nil {
112
+ return 0, err
113
+ }
114
+ written += n
115
+ }
116
+ return written, nil
117
+
118
+ } else {
119
+ err = writer.WriteHeader(&tar.Header{
120
+ Name: path,
121
+ Size: int64(pb.GetFilesize()),
122
+ Typeflag: tar.TypeReg,
123
+ Mode: 0644,
124
+ // TODO: set mode, dates, etc. when added to unixFS
125
+ })
126
+ if err != nil {
127
+ return 0, err
128
+ }
129
+
130
+ reader, err := uio.NewDagReader(dagnode, node.DAG)
131
+ if err != nil {
132
+ return 0, err
133
+ }
134
+
135
+ buf := make([]byte, 32*1024)
136
+ for {
137
+ nr, err := reader.Read(buf)
138
+ if nr > 0 {
139
+ nw, err := writer.Write(buf[:nr])
140
+ if err != nil {
141
+ return written, err
142
+ }
143
+ written += int64(nw)
144
+ signal <- struct{}{}
145
+ }
146
+ if err == io.EOF {
147
+ break
148
+ }
149
+ if err != nil {
150
+ return written, err
151
+ }
152
+ }
153
+ return written, nil
154
+ }
155
+}
156
+
157
+type getReader struct {
158
+ buf bytes.Buffer
159
+ closed bool
160
+ signalChan chan struct{}
161
+}
162
+
163
+func (i *getReader) Read(p []byte) (int, error) {
164
+ <-i.signalChan
165
+ n, err := i.buf.Read(p)
166
+ if err == io.EOF && !i.closed {
167
+ return n, nil
168
+ }
169
+ return n, err
170
+}
171
+
172
+func (i *getReader) Signal() {
173
+ i.signalChan <- struct{}{}
174
+}
175
+
176
+func (i *getReader) Close() {
177
+ i.closed = true
178
+}
core/commands/root.go
+1
@@ -82,6 +82,7 @@ var rootSubcommands = map[string]*cmds.Command{
82
"config": ConfigCmd,
83
"dht": DhtCmd,
84
"diag": DiagCmd,
85
+ "get": GetCmd,
86
"id": IDCmd,
87
"log": LogCmd,
88
"ls": LsCmd,