From e260d2fd066158dfd7ae8e5ae7e25ac202cdaa90 Mon Sep 17 00:00:00 2001 From: Lars Gierth <larsg@systemli.org> Date: Wed, 16 Nov 2016 06:21:15 +0100 Subject: [PATCH 1/2] coreapi: smarter way of dealing with the different APIs License: MIT Signed-off-by: Lars Gierth <larsg@systemli.org> --- core/coreapi/coreapi.go | 13 +++++++++++++ core/coreapi/interface/interface.go | 9 ++++----- core/coreapi/unixfs.go | 10 +--------- core/coreapi/unixfs_test.go | 2 +- core/corehttp/gateway.go | 2 +- core/corehttp/gateway_handler.go | 12 ++++++------ 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index f86b0e648a3..a44af4876e9 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -10,6 +10,19 @@ import ( ipld "gx/ipfs/QmYDscK7dmdo2GZ9aumS8s5auUUAH5mR1jvj5pYhWusfK7/go-ipld-node" ) +type CoreAPI struct { + node *core.IpfsNode +} + +func NewCoreAPI(n *core.IpfsNode) coreiface.CoreAPI { + api := &CoreAPI{n} + return api +} + +func (api *CoreAPI) Unixfs() coreiface.UnixfsAPI { + return (*UnixfsAPI)(api) +} + func resolve(ctx context.Context, n *core.IpfsNode, p string) (ipld.Node, error) { pp, err := path.ParsePath(p) if err != nil { diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index b506e65096a..7bf9d5c0ac6 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -9,11 +9,6 @@ import ( ipld "gx/ipfs/QmYDscK7dmdo2GZ9aumS8s5auUUAH5mR1jvj5pYhWusfK7/go-ipld-node" ) -// type CoreAPI interface { -// ID() CoreID -// Version() CoreVersion -// } - type Link ipld.Link type Reader interface { @@ -21,6 +16,10 @@ type Reader interface { io.Closer } +type CoreAPI interface { + Unixfs() UnixfsAPI +} + type UnixfsAPI interface { Add(context.Context, io.Reader) (*cid.Cid, error) Cat(context.Context, string) (Reader, error) diff --git a/core/coreapi/unixfs.go b/core/coreapi/unixfs.go index db97b2594cc..0ec8cf8519e 100644 --- a/core/coreapi/unixfs.go +++ b/core/coreapi/unixfs.go @@ -4,7 +4,6 @@ import ( "context" "io" - core "github.com/ipfs/go-ipfs/core" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" coreunix "github.com/ipfs/go-ipfs/core/coreunix" uio "github.com/ipfs/go-ipfs/unixfs/io" @@ -12,14 +11,7 @@ import ( cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid" ) -type UnixfsAPI struct { - node *core.IpfsNode -} - -func NewUnixfsAPI(n *core.IpfsNode) coreiface.UnixfsAPI { - api := &UnixfsAPI{n} - return api -} +type UnixfsAPI CoreAPI func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (*cid.Cid, error) { k, err := coreunix.AddWithContext(ctx, api.node, r) diff --git a/core/coreapi/unixfs_test.go b/core/coreapi/unixfs_test.go index 61270551045..3cda629afd6 100644 --- a/core/coreapi/unixfs_test.go +++ b/core/coreapi/unixfs_test.go @@ -41,7 +41,7 @@ func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.UnixfsAPI, error) { if err != nil { return nil, nil, err } - api := coreapi.NewUnixfsAPI(node) + api := coreapi.NewCoreAPI(node).Unixfs() return node, api, nil } diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index e212c183b3a..84b9b246745 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -28,7 +28,7 @@ func GatewayOption(writable bool, paths ...string) ServeOption { Headers: cfg.Gateway.HTTPHeaders, Writable: writable, PathPrefixes: cfg.Gateway.PathPrefixes, - }, coreapi.NewUnixfsAPI(n)) + }, coreapi.NewCoreAPI(n)) for _, p := range paths { mux.Handle(p+"/", gateway) diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 67dae923b79..03d5ca18f05 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -37,10 +37,10 @@ const ( type gatewayHandler struct { node *core.IpfsNode config GatewayConfig - api coreiface.UnixfsAPI + api coreiface.CoreAPI } -func newGatewayHandler(n *core.IpfsNode, c GatewayConfig, api coreiface.UnixfsAPI) *gatewayHandler { +func newGatewayHandler(n *core.IpfsNode, c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler { i := &gatewayHandler{ node: n, config: c, @@ -158,7 +158,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr ipnsHostname = true } - dr, err := i.api.Cat(ctx, urlPath) + dr, err := i.api.Unixfs().Cat(ctx, urlPath) dir := false switch err { case nil: @@ -218,7 +218,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - links, err := i.api.Ls(ctx, urlPath) + links, err := i.api.Unixfs().Ls(ctx, urlPath) if err != nil { internalWebError(w, err) return @@ -247,7 +247,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr } // return index page instead. - dr, err := i.api.Cat(ctx, p.String()) + dr, err := i.api.Unixfs().Cat(ctx, p.String()) if err != nil { internalWebError(w, err) return @@ -314,7 +314,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr } func (i *gatewayHandler) postHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - k, err := i.api.Add(ctx, r.Body) + k, err := i.api.Unixfs().Add(ctx, r.Body) if err != nil { internalWebError(w, err) return From ee45b8d32f90313901429e7536c143e20dd5ff79 Mon Sep 17 00:00:00 2001 From: Lars Gierth <larsg@systemli.org> Date: Fri, 17 Mar 2017 03:47:59 +0100 Subject: [PATCH 2/2] coreapi: make the interfaces path centric The new coreiface.Path maps a path to the cid.Cid resulting from a full path resolution. The path is internally represented as a go-ipfs/path.Path, but that doesn't matter to the outside. Apart from the path-to-CID mapping, it also aims to hold all resolved segment CIDs of the path. Right now it only exposes Root(), and only for flat paths a la /ipfs/Qmfoo. In other cases, the root is nil. In the future, resolution will internally use go-ipfs/path.Resolver.ResolvePathComponents and thus always return the proper resolved segments, via Root(), or a future Segments() func. - Add coreiface.Path with Cid() and Root(). - Add CoreAPI.ResolvePath() for getting a coreiface.Path. - All functions now expect and return coreiface.Path. - Add ParsePath() and ParseCid() for constructing a coreiface.Path. - Add coreiface.Node and Link which are simply go-ipld-node.Node and Link. - Add CoreAPI.ResolveNode() for getting a Node from a Path. License: MIT Signed-off-by: Lars Gierth <larsg@systemli.org> --- core/coreapi/coreapi.go | 60 ++++++++++++++++++++++++++--- core/coreapi/interface/interface.go | 19 +++++++-- core/coreapi/unixfs.go | 20 +++++++--- core/coreapi/unixfs_test.go | 58 +++++++++++++++------------- core/corehttp/gateway_handler.go | 26 ++++++------- 5 files changed, 128 insertions(+), 55 deletions(-) diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index a44af4876e9..980dca7c0f3 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -5,9 +5,9 @@ import ( core "github.com/ipfs/go-ipfs/core" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" - path "github.com/ipfs/go-ipfs/path" + ipfspath "github.com/ipfs/go-ipfs/path" - ipld "gx/ipfs/QmYDscK7dmdo2GZ9aumS8s5auUUAH5mR1jvj5pYhWusfK7/go-ipld-node" + cid "gx/ipfs/QmV5gPoRsjN1Gid3LMdNZTyfCtP2DsvqEbMAmz82RmmiGk/go-cid" ) type CoreAPI struct { @@ -23,17 +23,65 @@ func (api *CoreAPI) Unixfs() coreiface.UnixfsAPI { return (*UnixfsAPI)(api) } -func resolve(ctx context.Context, n *core.IpfsNode, p string) (ipld.Node, error) { - pp, err := path.ParsePath(p) +func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) { + p, err := api.ResolvePath(ctx, p) if err != nil { return nil, err } - dagnode, err := core.Resolve(ctx, n.Namesys, n.Resolver, pp) + node, err := api.node.DAG.Get(ctx, p.Cid()) + if err != nil { + return nil, err + } + return node, nil +} + +// TODO: store all of ipfspath.Resolver.ResolvePathComponents() in Path +func (api *CoreAPI) ResolvePath(ctx context.Context, p coreiface.Path) (coreiface.Path, error) { + if p.Resolved() { + return p, nil + } + + p2 := ipfspath.FromString(p.String()) + node, err := core.Resolve(ctx, api.node.Namesys, api.node.Resolver, p2) if err == core.ErrNoNamesys { return nil, coreiface.ErrOffline } else if err != nil { return nil, err } - return dagnode, nil + + var root *cid.Cid + if p2.IsJustAKey() { + root = node.Cid() + } + + return ResolvedPath(p.String(), node.Cid(), root), nil +} + +// Implements coreiface.Path +type path struct { + path ipfspath.Path + cid *cid.Cid + root *cid.Cid +} + +func ParsePath(p string) (coreiface.Path, error) { + pp, err := ipfspath.ParsePath(p) + if err != nil { + return nil, err + } + return &path{path: pp}, nil } + +func ParseCid(c *cid.Cid) coreiface.Path { + return &path{path: ipfspath.FromCid(c), cid: c, root: c} +} + +func ResolvedPath(p string, c *cid.Cid, r *cid.Cid) coreiface.Path { + return &path{path: ipfspath.FromString(p), cid: c, root: r} +} + +func (p *path) String() string { return p.path.String() } +func (p *path) Cid() *cid.Cid { return p.cid } +func (p *path) Root() *cid.Cid { return p.root } +func (p *path) Resolved() bool { return p.cid != nil } diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index 7bf9d5c0ac6..d72fc8a3b0b 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -9,6 +9,16 @@ import ( ipld "gx/ipfs/QmYDscK7dmdo2GZ9aumS8s5auUUAH5mR1jvj5pYhWusfK7/go-ipld-node" ) +type Path interface { + String() string + Cid() *cid.Cid + Root() *cid.Cid + Resolved() bool +} + +// TODO: should we really copy these? +// if we didn't, godoc would generate nice links straight to go-ipld-node +type Node ipld.Node type Link ipld.Link type Reader interface { @@ -18,12 +28,14 @@ type Reader interface { type CoreAPI interface { Unixfs() UnixfsAPI + ResolvePath(context.Context, Path) (Path, error) + ResolveNode(context.Context, Path) (Node, error) } type UnixfsAPI interface { - Add(context.Context, io.Reader) (*cid.Cid, error) - Cat(context.Context, string) (Reader, error) - Ls(context.Context, string) ([]*Link, error) + Add(context.Context, io.Reader) (Path, error) + Cat(context.Context, Path) (Reader, error) + Ls(context.Context, Path) ([]*Link, error) } // type ObjectAPI interface { @@ -49,5 +61,4 @@ type UnixfsAPI interface { // } 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") diff --git a/core/coreapi/unixfs.go b/core/coreapi/unixfs.go index 0ec8cf8519e..80e93b4e970 100644 --- a/core/coreapi/unixfs.go +++ b/core/coreapi/unixfs.go @@ -13,16 +13,20 @@ import ( type UnixfsAPI CoreAPI -func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (*cid.Cid, error) { +func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (coreiface.Path, error) { k, err := coreunix.AddWithContext(ctx, api.node, r) if err != nil { return nil, err } - return cid.Decode(k) + c, err := cid.Decode(k) + if err != nil { + return nil, err + } + return ParseCid(c), nil } -func (api *UnixfsAPI) Cat(ctx context.Context, p string) (coreiface.Reader, error) { - dagnode, err := resolve(ctx, api.node, p) +func (api *UnixfsAPI) Cat(ctx context.Context, p coreiface.Path) (coreiface.Reader, error) { + dagnode, err := api.core().ResolveNode(ctx, p) if err != nil { return nil, err } @@ -36,8 +40,8 @@ func (api *UnixfsAPI) Cat(ctx context.Context, p string) (coreiface.Reader, erro return r, nil } -func (api *UnixfsAPI) Ls(ctx context.Context, p string) ([]*coreiface.Link, error) { - dagnode, err := resolve(ctx, api.node, p) +func (api *UnixfsAPI) Ls(ctx context.Context, p coreiface.Path) ([]*coreiface.Link, error) { + dagnode, err := api.core().ResolveNode(ctx, p) if err != nil { return nil, err } @@ -49,3 +53,7 @@ func (api *UnixfsAPI) Ls(ctx context.Context, p string) ([]*coreiface.Link, erro } return links, nil } + +func (api *UnixfsAPI) core() coreiface.CoreAPI { + return (*CoreAPI)(api) +} diff --git a/core/coreapi/unixfs_test.go b/core/coreapi/unixfs_test.go index 3cda629afd6..7c2a64967e6 100644 --- a/core/coreapi/unixfs_test.go +++ b/core/coreapi/unixfs_test.go @@ -19,14 +19,14 @@ import ( ) // `echo -n 'hello, world!' | ipfs add` -var hello = "QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk" +var hello = coreapi.ResolvedPath("/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk", nil, nil) var helloStr = "hello, world!" // `ipfs object new unixfs-dir` -var emptyUnixfsDir = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn" +var emptyDir = coreapi.ResolvedPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn", nil, nil) // `echo -n | ipfs add` -var emptyUnixfsFile = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH" +var emptyFile = coreapi.ResolvedPath("/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", nil, nil) func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.UnixfsAPI, error) { r := &repo.Mock{ @@ -53,13 +53,13 @@ func TestAdd(t *testing.T) { } str := strings.NewReader(helloStr) - c, err := api.Add(ctx, str) + p, err := api.Add(ctx, str) if err != nil { t.Error(err) } - if c.String() != hello { - t.Fatalf("expected CID %s, got: %s", hello, c) + if p.String() != hello.String() { + t.Fatalf("expected path %s, got: %s", hello, p) } r, err := api.Cat(ctx, hello) @@ -85,13 +85,13 @@ func TestAddEmptyFile(t *testing.T) { } str := strings.NewReader("") - c, err := api.Add(ctx, str) + p, err := api.Add(ctx, str) if err != nil { t.Error(err) } - if c.String() != emptyUnixfsFile { - t.Fatalf("expected CID %s, got: %s", hello, c) + if p.String() != emptyFile.String() { + t.Fatalf("expected path %s, got: %s", hello, p) } } @@ -103,16 +103,17 @@ func TestCatBasic(t *testing.T) { } hr := strings.NewReader(helloStr) - k, err := coreunix.Add(node, hr) + p, err := coreunix.Add(node, hr) if err != nil { t.Fatal(err) } + p = "/ipfs/" + p - if k != hello { - t.Fatalf("expected CID %s, got: %s", hello, k) + if p != hello.String() { + t.Fatalf("expected CID %s, got: %s", hello, p) } - r, err := api.Cat(ctx, k) + r, err := api.Cat(ctx, hello) if err != nil { t.Fatal(err) } @@ -139,7 +140,7 @@ func TestCatEmptyFile(t *testing.T) { t.Fatal(err) } - r, err := api.Cat(ctx, emptyUnixfsFile) + r, err := api.Cat(ctx, emptyFile) if err != nil { t.Fatal(err) } @@ -165,8 +166,13 @@ func TestCatDir(t *testing.T) { if err != nil { t.Error(err) } + p := coreapi.ParseCid(c) + + if p.String() != emptyDir.String() { + t.Fatalf("expected path %s, got: %s", emptyDir, p) + } - _, err = api.Cat(ctx, c.String()) + _, err = api.Cat(ctx, emptyDir) if err != coreiface.ErrIsDir { t.Fatalf("expected ErrIsDir, got: %s", err) } @@ -184,7 +190,7 @@ func TestCatNonUnixfs(t *testing.T) { t.Error(err) } - _, err = api.Cat(ctx, c.String()) + _, err = api.Cat(ctx, coreapi.ParseCid(c)) if !strings.Contains(err.Error(), "proto: required field") { t.Fatalf("expected protobuf error, got: %s", err) } @@ -197,7 +203,7 @@ func TestCatOffline(t *testing.T) { t.Error(err) } - _, err = api.Cat(ctx, "/ipns/Qmfoobar") + _, err = api.Cat(ctx, coreapi.ResolvedPath("/ipns/Qmfoobar", nil, nil)) if err != coreiface.ErrOffline { t.Fatalf("expected ErrOffline, got: %", err) } @@ -211,17 +217,17 @@ func TestLs(t *testing.T) { } r := strings.NewReader("content-of-file") - p, _, err := coreunix.AddWrapped(node, r, "name-of-file") + k, _, err := coreunix.AddWrapped(node, r, "name-of-file") if err != nil { t.Error(err) } - parts := strings.Split(p, "/") + parts := strings.Split(k, "/") if len(parts) != 2 { - t.Errorf("unexpected path:", p) + t.Errorf("unexpected path:", k) } - k := parts[0] + p := coreapi.ResolvedPath("/ipfs/"+parts[0], nil, nil) - links, err := api.Ls(ctx, k) + links, err := api.Ls(ctx, p) if err != nil { t.Error(err) } @@ -236,7 +242,7 @@ func TestLs(t *testing.T) { 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()) + t.Fatalf("expected cid = QmX3qQVKxDGz3URVC3861Z3CKtQKGBn6ffXRBBWGMFz9Lr, got %s", links[0].Cid) } } @@ -247,12 +253,12 @@ func TestLsEmptyDir(t *testing.T) { t.Error(err) } - c, err := node.DAG.Add(unixfs.EmptyDirNode()) + _, err = node.DAG.Add(unixfs.EmptyDirNode()) if err != nil { t.Error(err) } - links, err := api.Ls(ctx, c.String()) + links, err := api.Ls(ctx, emptyDir) if err != nil { t.Error(err) } @@ -275,7 +281,7 @@ func TestLsNonUnixfs(t *testing.T) { t.Error(err) } - links, err := api.Ls(ctx, c.String()) + links, err := api.Ls(ctx, coreapi.ParseCid(c)) if err != nil { t.Error(err) } diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 03d5ca18f05..7c1c4ead737 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -12,6 +12,7 @@ import ( "time" core "github.com/ipfs/go-ipfs/core" + coreapi "github.com/ipfs/go-ipfs/core/coreapi" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" "github.com/ipfs/go-ipfs/importer" chunk "github.com/ipfs/go-ipfs/importer/chunk" @@ -158,7 +159,13 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr ipnsHostname = true } - dr, err := i.api.Unixfs().Cat(ctx, urlPath) + parsedPath, err := coreapi.ParsePath(urlPath) + if err != nil { + webError(w, "invalid ipfs path", err, http.StatusBadRequest) + return + } + + dr, err := i.api.Unixfs().Cat(ctx, parsedPath) dir := false switch err { case nil: @@ -218,7 +225,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - links, err := i.api.Unixfs().Ls(ctx, urlPath) + links, err := i.api.Unixfs().Ls(ctx, parsedPath) if err != nil { internalWebError(w, err) return @@ -240,14 +247,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - p, err := path.ParsePath(urlPath + "/index.html") - if err != nil { - internalWebError(w, err) - return - } - - // return index page instead. - dr, err := i.api.Unixfs().Cat(ctx, p.String()) + dr, err := i.api.Unixfs().Cat(ctx, coreapi.ParseCid(link.Cid)) if err != nil { internalWebError(w, err) return @@ -314,15 +314,15 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr } func (i *gatewayHandler) postHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - k, err := i.api.Unixfs().Add(ctx, r.Body) + p, err := i.api.Unixfs().Add(ctx, r.Body) if err != nil { internalWebError(w, err) return } i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", k.String()) - http.Redirect(w, r, ipfsPathPrefix+k.String(), http.StatusCreated) + w.Header().Set("IPFS-Hash", p.Cid().String()) + http.Redirect(w, r, p.String(), http.StatusCreated) } func (i *gatewayHandler) putHandler(w http.ResponseWriter, r *http.Request) {