|
| 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{} |
0 commit comments