Implement Unixfs.Get()
This commit was moved from ipfs/go-ipfs-http-client@b31bee083d1fc5002779d755b6a624b517d2b10c
Łukasz Magiera committed
Jan 8, 2019 at 17:47 UTC
6d9dea62825012678ab0f117ec3cfe28c96fdaf0
2 files changed
+231
-4
client/httpapi/apifile.go
new
+231
@@ -0,0 +1,231 @@
1
+package httpapi
2
+
3
+import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "github.com/ipfs/go-cid"
8
+ "io"
9
+
10
+ "github.com/ipfs/go-ipfs/core/coreapi/interface"
11
+
12
+ "github.com/ipfs/go-ipfs-files"
13
+ unixfspb "github.com/ipfs/go-unixfs/pb"
14
+)
15
+
16
+func (api *UnixfsAPI) Get(ctx context.Context, p iface.Path) (files.Node, error) {
17
+ if p.Mutable() { // use resolved path in case we are dealing with IPNS / MFS
18
+ var err error
19
+ p, err = api.core().ResolvePath(ctx, p)
20
+ if err != nil {
21
+ return nil, err
22
+ }
23
+ }
24
+
25
+ var stat struct{
26
+ Hash string
27
+ Type string
28
+ Size int64 // unixfs size
29
+ }
30
+ err := api.core().request("files/stat", p.String()). Exec(ctx, &stat)
31
+ if err != nil {
32
+ return nil, err
33
+ }
34
+
35
+ switch stat.Type {
36
+ case "file":
37
+ return api.getFile(ctx, p, stat.Size)
38
+ case "directory":
39
+ return api.getDir(ctx, p, stat.Size)
40
+ default:
41
+ return nil, fmt.Errorf("unsupported file type '%s'", stat.Type)
42
+ }
43
+}
44
+
45
+type apiFile struct {
46
+ ctx context.Context
47
+ core *HttpApi
48
+ size int64
49
+ path iface.Path
50
+
51
+ r io.ReadCloser
52
+ at int64
53
+}
54
+
55
+func (f *apiFile) reset() error {
56
+ if f.r != nil {
57
+ f.r.Close()
58
+ }
59
+ req := f.core.request("cat", f.path.String())
60
+ if f.at != 0 {
61
+ req.Option("offset", f.at)
62
+ }
63
+ resp, err := req.Send(f.ctx)
64
+ if err != nil {
65
+ return err
66
+ }
67
+ if resp.Error != nil {
68
+ return resp.Error
69
+ }
70
+ f.r = resp.Output
71
+ return nil
72
+}
73
+
74
+func (f *apiFile) Read(p []byte) (int, error) {
75
+ n, err := f.r.Read(p)
76
+ if n > 0 {
77
+ f.at += int64(n)
78
+ }
79
+ return n, err
80
+}
81
+
82
+func (f *apiFile) Seek(offset int64, whence int) (int64, error) {
83
+ panic("implement me") //TODO
84
+}
85
+
86
+func (f *apiFile) Close() error {
87
+ if f.r != nil {
88
+ return f.r.Close()
89
+ }
90
+ return nil
91
+}
92
+
93
+func (f *apiFile) Size() (int64, error) {
94
+ return f.size, nil
95
+}
96
+
97
+func (api *UnixfsAPI) getFile(ctx context.Context, p iface.Path, size int64) (files.Node, error) {
98
+ f := &apiFile{
99
+ ctx: ctx,
100
+ core: api.core(),
101
+ size: size,
102
+ path: p,
103
+ }
104
+
105
+ return f, f.reset()
106
+}
107
+
108
+type apiIter struct {
109
+ ctx context.Context
110
+ core *UnixfsAPI
111
+
112
+ err error
113
+
114
+ dec *json.Decoder
115
+ curFile files.Node
116
+ cur lsLink
117
+}
118
+
119
+func (it *apiIter) Err() error {
120
+ return it.err
121
+}
122
+
123
+func (it *apiIter) Name() string {
124
+ return it.cur.Name
125
+}
126
+
127
+func (it *apiIter) Next() bool {
128
+ var out lsOutput
129
+ if err := it.dec.Decode(&out); err != nil {
130
+ if err != io.EOF {
131
+ it.err = err
132
+ }
133
+ return false
134
+ }
135
+
136
+ if len(out.Objects) != 1 {
137
+ it.err = fmt.Errorf("len(out.Objects) != 1 (is %d)", len(out.Objects))
138
+ return false
139
+ }
140
+
141
+ if len(out.Objects[0].Links) != 1 {
142
+ it.err = fmt.Errorf("len(out.Objects[0].Links) != 1 (is %d)", len(out.Objects[0].Links))
143
+ return false
144
+ }
145
+
146
+ it.cur = out.Objects[0].Links[0]
147
+ c, err := cid.Parse(it.cur.Hash)
148
+ if err != nil {
149
+ it.err = err
150
+ return false
151
+ }
152
+
153
+ switch it.cur.Type {
154
+ case unixfspb.Data_HAMTShard:
155
+ fallthrough
156
+ case unixfspb.Data_Metadata:
157
+ fallthrough
158
+ case unixfspb.Data_Directory:
159
+ it.curFile, err = it.core.getDir(it.ctx, iface.IpfsPath(c), int64(it.cur.Size))
160
+ if err != nil {
161
+ it.err = err
162
+ return false
163
+ }
164
+ case unixfspb.Data_File:
165
+ it.curFile, err = it.core.getFile(it.ctx, iface.IpfsPath(c), int64(it.cur.Size))
166
+ if err != nil {
167
+ it.err = err
168
+ return false
169
+ }
170
+ default:
171
+ it.err = fmt.Errorf("file type %d not supported", it.cur.Type)
172
+ return false
173
+ }
174
+ return true
175
+}
176
+
177
+func (it *apiIter) Node() files.Node {
178
+ return it.curFile
179
+}
180
+
181
+type apiDir struct {
182
+ ctx context.Context
183
+ core *UnixfsAPI
184
+ size int64
185
+ path iface.Path
186
+
187
+ dec *json.Decoder
188
+}
189
+
190
+func (d *apiDir) Close() error {
191
+ return nil
192
+}
193
+
194
+func (d *apiDir) Size() (int64, error) {
195
+ return d.size, nil
196
+}
197
+
198
+func (d *apiDir) Entries() files.DirIterator {
199
+ return &apiIter{
200
+ ctx: d.ctx,
201
+ core: d.core,
202
+ dec: d.dec,
203
+ }
204
+}
205
+
206
+func (api *UnixfsAPI) getDir(ctx context.Context, p iface.Path, size int64) (files.Node, error) {
207
+ resp, err := api.core().request("ls", p.String()).
208
+ Option("resolve-size", true).
209
+ Option("stream", true).Send(ctx)
210
+
211
+ if err != nil {
212
+ return nil, err
213
+ }
214
+ if resp.Error != nil {
215
+ return nil, resp.Error
216
+ }
217
+
218
+ d := &apiDir{
219
+ ctx: ctx,
220
+ core: api,
221
+ size: size,
222
+ path: p,
223
+
224
+ dec: json.NewDecoder(resp.Output),
225
+ }
226
+
227
+ return d, nil
228
+}
229
+
230
+var _ files.File = &apiFile{}
231
+var _ files.Directory = &apiDir{}
client/httpapi/unixfs.go
-4
@@ -85,10 +85,6 @@ func (api *UnixfsAPI) Add(ctx context.Context, f files.Node, opts ...caopts.Unix
85
return iface.IpfsPath(c), nil
86
}
87
88
-func (api *UnixfsAPI) Get(context.Context, iface.Path) (files.Node, error) {
89
- panic("implement me")
90
-}
91
-
88
type lsLink struct {
89
Name, Hash string
90
Size uint64