-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
coreapi: get going, add Cat() and Ls()
License: MIT Signed-off-by: Lars Gierth <larsg@systemli.org>
- Loading branch information
Lars Gierth
committed
Oct 28, 2016
1 parent
17c629d
commit 671b7c5
Showing
4 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package coreapi | ||
|
||
import ( | ||
"context" | ||
|
||
core "github.com/ipfs/go-ipfs/core" | ||
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" | ||
path "github.com/ipfs/go-ipfs/path" | ||
|
||
ipld "gx/ipfs/QmU7bFWQ793qmvNy7outdCaMfSDNk8uqhx4VNrxYj5fj5g/go-ipld-node" | ||
) | ||
|
||
func resolve(ctx context.Context, n *core.IpfsNode, p string) (ipld.Node, error) { | ||
pp, err := path.ParsePath(p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
dagnode, err := core.Resolve(ctx, n.Namesys, n.Resolver, pp) | ||
if err == core.ErrNoNamesys { | ||
return nil, coreiface.ErrOffline | ||
} else if err != nil { | ||
return nil, err | ||
} | ||
return dagnode, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package iface | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"io" | ||
|
||
cid "gx/ipfs/QmXfiyr2RWEXpVDdaYnD2HNiBk6UBddsvEP4RPfXb6nGqY/go-cid" | ||
) | ||
|
||
// type CoreAPI interface { | ||
// ID() CoreID | ||
// Version() CoreVersion | ||
// } | ||
|
||
type Link struct { | ||
Name string | ||
Size uint64 | ||
Cid *cid.Cid | ||
} | ||
|
||
type Reader interface { | ||
io.ReadSeeker | ||
io.Closer | ||
} | ||
|
||
type UnixfsAPI interface { | ||
Cat(context.Context, string) (Reader, error) | ||
Ls(context.Context, string) ([]*Link, error) | ||
} | ||
|
||
// type ObjectAPI interface { | ||
// New() (cid.Cid, Object) | ||
// Get(string) (Object, error) | ||
// Links(string) ([]*Link, error) | ||
// Data(string) (Reader, error) | ||
// Stat(string) (ObjectStat, error) | ||
// Put(Object) (cid.Cid, error) | ||
// SetData(string, Reader) (cid.Cid, error) | ||
// AppendData(string, Data) (cid.Cid, error) | ||
// AddLink(string, string, string) (cid.Cid, error) | ||
// RmLink(string, string) (cid.Cid, error) | ||
// } | ||
|
||
// type ObjectStat struct { | ||
// Cid cid.Cid | ||
// NumLinks int | ||
// BlockSize int | ||
// LinksSize int | ||
// DataSize int | ||
// CumulativeSize int | ||
// } | ||
|
||
var ErrIsDir = errors.New("object is a directory") | ||
var ErrIsNonDag = errors.New("not a merkledag object") | ||
var ErrOffline = errors.New("can't resolve, ipfs node is offline") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package coreapi | ||
|
||
import ( | ||
"context" | ||
|
||
core "github.com/ipfs/go-ipfs/core" | ||
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" | ||
mdag "github.com/ipfs/go-ipfs/merkledag" | ||
uio "github.com/ipfs/go-ipfs/unixfs/io" | ||
) | ||
|
||
type UnixfsAPI struct { | ||
node *core.IpfsNode | ||
} | ||
|
||
func NewUnixfsAPI(n *core.IpfsNode) coreiface.UnixfsAPI { | ||
api := &UnixfsAPI{n} | ||
return api | ||
} | ||
|
||
func (api *UnixfsAPI) Cat(ctx context.Context, p string) (coreiface.Reader, error) { | ||
dagnode, err := resolve(ctx, api.node, p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
_, ok := dagnode.(*mdag.ProtoNode) | ||
if !ok { | ||
return nil, coreiface.ErrIsNonDag | ||
} | ||
|
||
r, err := uio.NewDagReader(ctx, dagnode, api.node.DAG) | ||
if err == uio.ErrIsDir { | ||
return nil, coreiface.ErrIsDir | ||
} else if err != nil { | ||
return nil, err | ||
} | ||
return r, nil | ||
} | ||
|
||
func (api *UnixfsAPI) Ls(ctx context.Context, p string) ([]*coreiface.Link, error) { | ||
dagnode, err := resolve(ctx, api.node, p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
l := dagnode.Links() | ||
links := make([]*coreiface.Link, len(l)) | ||
for i, l := range l { | ||
links[i] = &coreiface.Link{l.Name, l.Size, l.Cid} | ||
} | ||
return links, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
package coreapi_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"io" | ||
"strings" | ||
"testing" | ||
|
||
core "github.com/ipfs/go-ipfs/core" | ||
coreapi "github.com/ipfs/go-ipfs/core/coreapi" | ||
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" | ||
coreunix "github.com/ipfs/go-ipfs/core/coreunix" | ||
mdag "github.com/ipfs/go-ipfs/merkledag" | ||
repo "github.com/ipfs/go-ipfs/repo" | ||
config "github.com/ipfs/go-ipfs/repo/config" | ||
testutil "github.com/ipfs/go-ipfs/thirdparty/testutil" | ||
unixfs "github.com/ipfs/go-ipfs/unixfs" | ||
) | ||
|
||
// `ipfs object new unixfs-dir` | ||
var emptyUnixfsDir = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" | ||
|
||
// `echo -n | ipfs add` | ||
var emptyUnixfsFile = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH" | ||
|
||
func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.UnixfsAPI, error) { | ||
r := &repo.Mock{ | ||
C: config.Config{ | ||
Identity: config.Identity{ | ||
PeerID: "Qmfoo", // required by offline node | ||
}, | ||
}, | ||
D: testutil.ThreadSafeCloserMapDatastore(), | ||
} | ||
node, err := core.NewNode(ctx, &core.BuildCfg{Repo: r}) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
api := coreapi.NewUnixfsAPI(node) | ||
return node, api, nil | ||
} | ||
|
||
func TestCatBasic(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
hello := "hello, world!" | ||
hr := strings.NewReader(hello) | ||
k, err := coreunix.Add(node, hr) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
r, err := api.Cat(ctx, k) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
buf := make([]byte, len(hello)) | ||
n, err := io.ReadFull(r, buf) | ||
if err != nil && err != io.EOF { | ||
t.Error(err) | ||
} | ||
if string(buf) != hello { | ||
t.Fatalf("expected [hello, world!], got [%s] [err=%s]", string(buf), n, err) | ||
} | ||
} | ||
|
||
func TestCatEmptyFile(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
_, err = coreunix.Add(node, strings.NewReader("")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
r, err := api.Cat(ctx, emptyUnixfsFile) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
buf := make([]byte, 1) // non-zero so that Read() actually tries to read | ||
n, err := io.ReadFull(r, buf) | ||
if err != nil && err != io.EOF { | ||
t.Error(err) | ||
} | ||
if !bytes.HasPrefix(buf, []byte{0x00}) { | ||
t.Fatalf("expected empty data, got [%s] [read=%d]", buf, n) | ||
} | ||
} | ||
|
||
func TestCatDir(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
c, err := node.DAG.Add(unixfs.EmptyDirNode()) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
_, err = api.Cat(ctx, c.String()) | ||
if err != coreiface.ErrIsDir { | ||
t.Fatalf("expected ErrIsDir, got: %s", err) | ||
} | ||
} | ||
|
||
func TestCatNonUnixfs(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
c, err := node.DAG.Add(new(mdag.ProtoNode)) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
_, err = api.Cat(ctx, c.String()) | ||
if !strings.Contains(err.Error(), "proto: required field") { | ||
t.Fatalf("expected protobuf error, got: %s", err) | ||
} | ||
} | ||
|
||
func TestCatOffline(t *testing.T) { | ||
ctx := context.Background() | ||
_, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
_, err = api.Cat(ctx, "/ipns/Qmfoobar") | ||
if err != coreiface.ErrOffline { | ||
t.Fatalf("expected ErrOffline, got: %", err) | ||
} | ||
} | ||
|
||
func TestLs(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
r := strings.NewReader("content-of-file") | ||
p, _, err := coreunix.AddWrapped(node, r, "name-of-file") | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
parts := strings.Split(p, "/") | ||
if len(parts) != 2 { | ||
t.Errorf("unexpected path:", p) | ||
} | ||
k := parts[0] | ||
|
||
links, err := api.Ls(ctx, k) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if len(links) != 1 { | ||
t.Fatalf("expected 1 link, got %d", len(links)) | ||
} | ||
if links[0].Size != 23 { | ||
t.Fatalf("expected size = 23, got %d", links[0].Size) | ||
} | ||
if links[0].Name != "name-of-file" { | ||
t.Fatalf("expected name = name-of-file, got %s", links[0].Name) | ||
} | ||
if links[0].Cid.String() != "QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr" { | ||
t.Fatalf("expected cid = QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr, got %s", links[0].Cid.String()) | ||
} | ||
} | ||
|
||
func TestLsEmptyDir(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
c, err := node.DAG.Add(unixfs.EmptyDirNode()) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
links, err := api.Ls(ctx, c.String()) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if len(links) != 0 { | ||
t.Fatalf("expected 0 links, got %d", len(links)) | ||
} | ||
} | ||
|
||
// TODO(lgierth) this should test properly, with len(links) > 0 | ||
func TestLsNonUnixfs(t *testing.T) { | ||
ctx := context.Background() | ||
node, api, err := makeAPI(ctx) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
c, err := node.DAG.Add(new(mdag.ProtoNode)) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
links, err := api.Ls(ctx, c.String()) | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if len(links) != 0 { | ||
t.Fatalf("expected 0 links, got %d", len(links)) | ||
} | ||
} |