diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go new file mode 100644 index 00000000000..69b1da5daad --- /dev/null +++ b/core/coreapi/coreapi.go @@ -0,0 +1,60 @@ +package coreapi + +import ( + "context" + + core "github.com/ipfs/go-ipfs/core" + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + dag "github.com/ipfs/go-ipfs/merkledag" + path "github.com/ipfs/go-ipfs/path" + uio "github.com/ipfs/go-ipfs/unixfs/io" + cid "gx/ipfs/QmfSc2xehWmWLnwwYR91Y8QF4xdASypTFVknutoKQS3GHp/go-cid" +) + +type UnixfsAPI struct { + Context context.Context + Node *core.IpfsNode +} + +func (api *UnixfsAPI) resolve(p string) (*dag.Node, error) { + pp, err := path.ParsePath(p) + if err != nil { + return nil, err + } + + dagnode, err := core.Resolve(api.Context, api.Node, pp) + if err == core.ErrNoNamesys { + return nil, coreiface.ErrOffline + } else if err != nil { + return nil, err + } + return dagnode, nil +} + +func (api *UnixfsAPI) Cat(p string) (coreiface.Reader, error) { + dagnode, err := api.resolve(p) + if err != nil { + return nil, err + } + + r, err := uio.NewDagReader(api.Context, 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(p string) ([]*coreiface.Link, error) { + dagnode, err := api.resolve(p) + if err != nil { + return nil, err + } + + links := make([]*coreiface.Link, len(dagnode.Links)) + for i, l := range dagnode.Links { + links[i] = &coreiface.Link{l.Name, l.Size, cid.NewCidV0(l.Hash)} + } + return links, nil +} diff --git a/core/coreapi/coreapi_test.go b/core/coreapi/coreapi_test.go new file mode 100644 index 00000000000..7088a5f1487 --- /dev/null +++ b/core/coreapi/coreapi_test.go @@ -0,0 +1,118 @@ +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" + repo "github.com/ipfs/go-ipfs/repo" + config "github.com/ipfs/go-ipfs/repo/config" + testutil "github.com/ipfs/go-ipfs/thirdparty/testutil" +) + +// `ipfs object new unixfs-dir` +var emptyUnixfsDir = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" + +// `echo -n | ipfs add` +var emptyUnixfsFile = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH" + +func makeAPI(ctx context.Context) (*core.IpfsNode, *coreapi.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.UnixfsAPI{Node: node, Context: ctx} + return node, api, nil +} + +func TestCatBasic(t *testing.T) { + node, api, err := makeAPI(context.Background()) + 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(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) { + node, api, err := makeAPI(context.Background()) + if err != nil { + t.Fatal(err) + } + + _, err = coreunix.Add(node, strings.NewReader("")) + if err != nil { + t.Fatal(err) + } + + r, err := api.Cat(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) { + t.Skip("TODO: implement me") +} + +func TestCatNonUnixfs(t *testing.T) { + t.Skip("TODO: implement me") +} + +func TestCatOffline(t *testing.T) { + t.Skip("TODO: implement me") +} + +func TestLs(t *testing.T) { + t.Skip("TODO: implement me") +} + +func TestLsEmpty(t *testing.T) { + t.Skip("TODO: implement me") +} + +func TestLsNonUnixfs(t *testing.T) { + t.Skip("TODO: implement me") +} diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go new file mode 100644 index 00000000000..bd1eca0eca7 --- /dev/null +++ b/core/coreapi/interface/interface.go @@ -0,0 +1,54 @@ +package iface + +import ( + "errors" + "io" + + cid "gx/ipfs/QmfSc2xehWmWLnwwYR91Y8QF4xdASypTFVknutoKQS3GHp/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(string) (Reader, error) + Ls(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 ErrOffline = errors.New("can't resolve, ipfs node is offline")