@cryptotaxi247 / kubo / commits / 46239e821

coreapi: get going, add Cat() and Ls()

License: MIT Signed-off-by: Lars Gierth <larsg@systemli.org>

Lars Gierth committed Sep 11, 2016 at 05:09 UTC 46239e821530cf6e26d05b82a4ad18e406cfe43b
4 files changed +358
core/coreapi/coreapi.go new
+26
@@ -0,0 +1,26 @@
1 +package coreapi
2 +
3 +import (
4 + "context"
5 +
6 + core "github.com/ipfs/go-ipfs/core"
7 + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
8 + path "github.com/ipfs/go-ipfs/path"
9 +
10 + ipld "gx/ipfs/QmU7bFWQ793qmvNy7outdCaMfSDNk8uqhx4VNrxYj5fj5g/go-ipld-node"
11 +)
12 +
13 +func resolve(ctx context.Context, n *core.IpfsNode, p string) (ipld.Node, error) {
14 + pp, err := path.ParsePath(p)
15 + if err != nil {
16 + return nil, err
17 + }
18 +
19 + dagnode, err := core.Resolve(ctx, n.Namesys, n.Resolver, pp)
20 + if err == core.ErrNoNamesys {
21 + return nil, coreiface.ErrOffline
22 + } else if err != nil {
23 + return nil, err
24 + }
25 + return dagnode, nil
26 +}
core/coreapi/interface/interface.go new
+56
@@ -0,0 +1,56 @@
1 +package iface
2 +
3 +import (
4 + "context"
5 + "errors"
6 + "io"
7 +
8 + cid "gx/ipfs/QmXfiyr2RWEXpVDdaYnD2HNiBk6UBddsvEP4RPfXb6nGqY/go-cid"
9 +)
10 +
11 +// type CoreAPI interface {
12 +// ID() CoreID
13 +// Version() CoreVersion
14 +// }
15 +
16 +type Link struct {
17 + Name string
18 + Size uint64
19 + Cid *cid.Cid
20 +}
21 +
22 +type Reader interface {
23 + io.ReadSeeker
24 + io.Closer
25 +}
26 +
27 +type UnixfsAPI interface {
28 + Cat(context.Context, string) (Reader, error)
29 + Ls(context.Context, string) ([]*Link, error)
30 +}
31 +
32 +// type ObjectAPI interface {
33 +// New() (cid.Cid, Object)
34 +// Get(string) (Object, error)
35 +// Links(string) ([]*Link, error)
36 +// Data(string) (Reader, error)
37 +// Stat(string) (ObjectStat, error)
38 +// Put(Object) (cid.Cid, error)
39 +// SetData(string, Reader) (cid.Cid, error)
40 +// AppendData(string, Data) (cid.Cid, error)
41 +// AddLink(string, string, string) (cid.Cid, error)
42 +// RmLink(string, string) (cid.Cid, error)
43 +// }
44 +
45 +// type ObjectStat struct {
46 +// Cid cid.Cid
47 +// NumLinks int
48 +// BlockSize int
49 +// LinksSize int
50 +// DataSize int
51 +// CumulativeSize int
52 +// }
53 +
54 +var ErrIsDir = errors.New("object is a directory")
55 +var ErrIsNonDag = errors.New("not a merkledag object")
56 +var ErrOffline = errors.New("can't resolve, ipfs node is offline")
core/coreapi/unixfs.go new
+47
@@ -0,0 +1,47 @@
1 +package coreapi
2 +
3 +import (
4 + "context"
5 +
6 + core "github.com/ipfs/go-ipfs/core"
7 + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
8 + uio "github.com/ipfs/go-ipfs/unixfs/io"
9 +)
10 +
11 +type UnixfsAPI struct {
12 + node *core.IpfsNode
13 +}
14 +
15 +func NewUnixfsAPI(n *core.IpfsNode) coreiface.UnixfsAPI {
16 + api := &UnixfsAPI{n}
17 + return api
18 +}
19 +
20 +func (api *UnixfsAPI) Cat(ctx context.Context, p string) (coreiface.Reader, error) {
21 + dagnode, err := resolve(ctx, api.node, p)
22 + if err != nil {
23 + return nil, err
24 + }
25 +
26 + r, err := uio.NewDagReader(ctx, dagnode, api.node.DAG)
27 + if err == uio.ErrIsDir {
28 + return nil, coreiface.ErrIsDir
29 + } else if err != nil {
30 + return nil, err
31 + }
32 + return r, nil
33 +}
34 +
35 +func (api *UnixfsAPI) Ls(ctx context.Context, p string) ([]*coreiface.Link, error) {
36 + dagnode, err := resolve(ctx, api.node, p)
37 + if err != nil {
38 + return nil, err
39 + }
40 +
41 + l := dagnode.Links()
42 + links := make([]*coreiface.Link, len(l))
43 + for i, l := range l {
44 + links[i] = &coreiface.Link{l.Name, l.Size, l.Cid}
45 + }
46 + return links, nil
47 +}
core/coreapi/unixfs_test.go new
+229
@@ -0,0 +1,229 @@
1 +package coreapi_test
2 +
3 +import (
4 + "bytes"
5 + "context"
6 + "io"
7 + "strings"
8 + "testing"
9 +
10 + core "github.com/ipfs/go-ipfs/core"
11 + coreapi "github.com/ipfs/go-ipfs/core/coreapi"
12 + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
13 + coreunix "github.com/ipfs/go-ipfs/core/coreunix"
14 + mdag "github.com/ipfs/go-ipfs/merkledag"
15 + repo "github.com/ipfs/go-ipfs/repo"
16 + config "github.com/ipfs/go-ipfs/repo/config"
17 + testutil "github.com/ipfs/go-ipfs/thirdparty/testutil"
18 + unixfs "github.com/ipfs/go-ipfs/unixfs"
19 +)
20 +
21 +// `ipfs object new unixfs-dir`
22 +var emptyUnixfsDir = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"
23 +
24 +// `echo -n | ipfs add`
25 +var emptyUnixfsFile = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH"
26 +
27 +func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.UnixfsAPI, error) {
28 + r := &repo.Mock{
29 + C: config.Config{
30 + Identity: config.Identity{
31 + PeerID: "Qmfoo", // required by offline node
32 + },
33 + },
34 + D: testutil.ThreadSafeCloserMapDatastore(),
35 + }
36 + node, err := core.NewNode(ctx, &core.BuildCfg{Repo: r})
37 + if err != nil {
38 + return nil, nil, err
39 + }
40 + api := coreapi.NewUnixfsAPI(node)
41 + return node, api, nil
42 +}
43 +
44 +func TestCatBasic(t *testing.T) {
45 + ctx := context.Background()
46 + node, api, err := makeAPI(ctx)
47 + if err != nil {
48 + t.Fatal(err)
49 + }
50 +
51 + hello := "hello, world!"
52 + hr := strings.NewReader(hello)
53 + k, err := coreunix.Add(node, hr)
54 + if err != nil {
55 + t.Fatal(err)
56 + }
57 +
58 + r, err := api.Cat(ctx, k)
59 + if err != nil {
60 + t.Fatal(err)
61 + }
62 +
63 + buf := make([]byte, len(hello))
64 + n, err := io.ReadFull(r, buf)
65 + if err != nil && err != io.EOF {
66 + t.Error(err)
67 + }
68 + if string(buf) != hello {
69 + t.Fatalf("expected [hello, world!], got [%s] [err=%s]", string(buf), n, err)
70 + }
71 +}
72 +
73 +func TestCatEmptyFile(t *testing.T) {
74 + ctx := context.Background()
75 + node, api, err := makeAPI(ctx)
76 + if err != nil {
77 + t.Fatal(err)
78 + }
79 +
80 + _, err = coreunix.Add(node, strings.NewReader(""))
81 + if err != nil {
82 + t.Fatal(err)
83 + }
84 +
85 + r, err := api.Cat(ctx, emptyUnixfsFile)
86 + if err != nil {
87 + t.Fatal(err)
88 + }
89 +
90 + buf := make([]byte, 1) // non-zero so that Read() actually tries to read
91 + n, err := io.ReadFull(r, buf)
92 + if err != nil && err != io.EOF {
93 + t.Error(err)
94 + }
95 + if !bytes.HasPrefix(buf, []byte{0x00}) {
96 + t.Fatalf("expected empty data, got [%s] [read=%d]", buf, n)
97 + }
98 +}
99 +
100 +func TestCatDir(t *testing.T) {
101 + ctx := context.Background()
102 + node, api, err := makeAPI(ctx)
103 + if err != nil {
104 + t.Error(err)
105 + }
106 +
107 + c, err := node.DAG.Add(unixfs.EmptyDirNode())
108 + if err != nil {
109 + t.Error(err)
110 + }
111 +
112 + _, err = api.Cat(ctx, c.String())
113 + if err != coreiface.ErrIsDir {
114 + t.Fatalf("expected ErrIsDir, got: %s", err)
115 + }
116 +}
117 +
118 +func TestCatNonUnixfs(t *testing.T) {
119 + ctx := context.Background()
120 + node, api, err := makeAPI(ctx)
121 + if err != nil {
122 + t.Error(err)
123 + }
124 +
125 + c, err := node.DAG.Add(new(mdag.ProtoNode))
126 + if err != nil {
127 + t.Error(err)
128 + }
129 +
130 + _, err = api.Cat(ctx, c.String())
131 + if !strings.Contains(err.Error(), "proto: required field") {
132 + t.Fatalf("expected protobuf error, got: %s", err)
133 + }
134 +}
135 +
136 +func TestCatOffline(t *testing.T) {
137 + ctx := context.Background()
138 + _, api, err := makeAPI(ctx)
139 + if err != nil {
140 + t.Error(err)
141 + }
142 +
143 + _, err = api.Cat(ctx, "/ipns/Qmfoobar")
144 + if err != coreiface.ErrOffline {
145 + t.Fatalf("expected ErrOffline, got: %", err)
146 + }
147 +}
148 +
149 +func TestLs(t *testing.T) {
150 + ctx := context.Background()
151 + node, api, err := makeAPI(ctx)
152 + if err != nil {
153 + t.Error(err)
154 + }
155 +
156 + r := strings.NewReader("content-of-file")
157 + p, _, err := coreunix.AddWrapped(node, r, "name-of-file")
158 + if err != nil {
159 + t.Error(err)
160 + }
161 + parts := strings.Split(p, "/")
162 + if len(parts) != 2 {
163 + t.Errorf("unexpected path:", p)
164 + }
165 + k := parts[0]
166 +
167 + links, err := api.Ls(ctx, k)
168 + if err != nil {
169 + t.Error(err)
170 + }
171 +
172 + if len(links) != 1 {
173 + t.Fatalf("expected 1 link, got %d", len(links))
174 + }
175 + if links[0].Size != 23 {
176 + t.Fatalf("expected size = 23, got %d", links[0].Size)
177 + }
178 + if links[0].Name != "name-of-file" {
179 + t.Fatalf("expected name = name-of-file, got %s", links[0].Name)
180 + }
181 + if links[0].Cid.String() != "QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr" {
182 + t.Fatalf("expected cid = QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr, got %s", links[0].Cid.String())
183 + }
184 +}
185 +
186 +func TestLsEmptyDir(t *testing.T) {
187 + ctx := context.Background()
188 + node, api, err := makeAPI(ctx)
189 + if err != nil {
190 + t.Error(err)
191 + }
192 +
193 + c, err := node.DAG.Add(unixfs.EmptyDirNode())
194 + if err != nil {
195 + t.Error(err)
196 + }
197 +
198 + links, err := api.Ls(ctx, c.String())
199 + if err != nil {
200 + t.Error(err)
201 + }
202 +
203 + if len(links) != 0 {
204 + t.Fatalf("expected 0 links, got %d", len(links))
205 + }
206 +}
207 +
208 +// TODO(lgierth) this should test properly, with len(links) > 0
209 +func TestLsNonUnixfs(t *testing.T) {
210 + ctx := context.Background()
211 + node, api, err := makeAPI(ctx)
212 + if err != nil {
213 + t.Error(err)
214 + }
215 +
216 + c, err := node.DAG.Add(new(mdag.ProtoNode))
217 + if err != nil {
218 + t.Error(err)
219 + }
220 +
221 + links, err := api.Ls(ctx, c.String())
222 + if err != nil {
223 + t.Error(err)
224 + }
225 +
226 + if len(links) != 0 {
227 + t.Fatalf("expected 0 links, got %d", len(links))
228 + }
229 +}