diff --git a/core/coreapi/block.go b/core/coreapi/block.go index 497b0099c84..69f5060c877 100644 --- a/core/coreapi/block.go +++ b/core/coreapi/block.go @@ -20,11 +20,11 @@ import ( type BlockAPI CoreAPI type BlockStat struct { - path coreiface.Path + path coreiface.ResolvedPath size int } -func (api *BlockAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.BlockPutOption) (coreiface.Path, error) { +func (api *BlockAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.BlockPutOption) (coreiface.ResolvedPath, error) { settings, err := caopts.BlockPutOptions(opts...) if err != nil { return nil, err @@ -65,11 +65,16 @@ func (api *BlockAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.Bloc return nil, err } - return ParseCid(b.Cid()), nil + return coreiface.IpldPath(b.Cid()), nil } func (api *BlockAPI) Get(ctx context.Context, p coreiface.Path) (io.Reader, error) { - b, err := api.node.Blocks.GetBlock(ctx, p.Cid()) + rp, err := api.core().ResolvePath(ctx, p) + if err != nil { + return nil, err + } + + b, err := api.node.Blocks.GetBlock(ctx, rp.Cid()) if err != nil { return nil, err } @@ -78,11 +83,16 @@ func (api *BlockAPI) Get(ctx context.Context, p coreiface.Path) (io.Reader, erro } func (api *BlockAPI) Rm(ctx context.Context, p coreiface.Path, opts ...caopts.BlockRmOption) error { + rp, err := api.core().ResolvePath(ctx, p) + if err != nil { + return err + } + settings, err := caopts.BlockRmOptions(opts...) if err != nil { return err } - cids := []*cid.Cid{p.Cid()} + cids := []*cid.Cid{rp.Cid()} o := util.RmBlocksOpts{Force: settings.Force} out, err := util.RmBlocks(api.node.Blockstore, api.node.Pinning, cids, o) @@ -111,13 +121,18 @@ func (api *BlockAPI) Rm(ctx context.Context, p coreiface.Path, opts ...caopts.Bl } func (api *BlockAPI) Stat(ctx context.Context, p coreiface.Path) (coreiface.BlockStat, error) { - b, err := api.node.Blocks.GetBlock(ctx, p.Cid()) + rp, err := api.core().ResolvePath(ctx, p) + if err != nil { + return nil, err + } + + b, err := api.node.Blocks.GetBlock(ctx, rp.Cid()) if err != nil { return nil, err } return &BlockStat{ - path: ParseCid(b.Cid()), + path: coreiface.IpldPath(b.Cid()), size: len(b.RawData()), }, nil } @@ -126,6 +141,10 @@ func (bs *BlockStat) Size() int { return bs.size } -func (bs *BlockStat) Path() coreiface.Path { +func (bs *BlockStat) Path() coreiface.ResolvedPath { return bs.path } + +func (api *BlockAPI) core() coreiface.CoreAPI { + return (*CoreAPI)(api) +} diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index 22c13b84ecc..e9ec439518a 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -14,17 +14,8 @@ Interfaces here aren't yet completely stable. package coreapi import ( - "context" - core "github.com/ipfs/go-ipfs/core" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" - namesys "github.com/ipfs/go-ipfs/namesys" - ipfspath "github.com/ipfs/go-ipfs/path" - resolver "github.com/ipfs/go-ipfs/path/resolver" - uio "github.com/ipfs/go-ipfs/unixfs/io" - - cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" - ipld "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" ) type CoreAPI struct { @@ -71,86 +62,3 @@ func (api *CoreAPI) Object() coreiface.ObjectAPI { func (api *CoreAPI) Pin() coreiface.PinAPI { return (*PinAPI)(api) } - -// ResolveNode resolves the path `p` using Unixfx resolver, gets and returns the -// resolved Node. -func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (ipld.Node, error) { - return resolveNode(ctx, api.node.DAG, api.node.Namesys, p) -} - -func resolveNode(ctx context.Context, ng ipld.NodeGetter, nsys namesys.NameSystem, p coreiface.Path) (ipld.Node, error) { - p, err := resolvePath(ctx, ng, nsys, p) - if err != nil { - return nil, err - } - - node, err := ng.Get(ctx, p.Cid()) - if err != nil { - return nil, err - } - return node, nil -} - -// ResolvePath resolves the path `p` using Unixfs resolver, returns the -// resolved path. -// TODO: store all of ipfspath.Resolver.ResolvePathComponents() in Path -func (api *CoreAPI) ResolvePath(ctx context.Context, p coreiface.Path) (coreiface.Path, error) { - return resolvePath(ctx, api.node.DAG, api.node.Namesys, p) -} - -func resolvePath(ctx context.Context, ng ipld.NodeGetter, nsys namesys.NameSystem, p coreiface.Path) (coreiface.Path, error) { - if p.Resolved() { - return p, nil - } - - r := &resolver.Resolver{ - DAG: ng, - ResolveOnce: uio.ResolveUnixfsOnce, - } - - p2 := ipfspath.FromString(p.String()) - node, err := core.Resolve(ctx, nsys, r, p2) - if err == core.ErrNoNamesys { - return nil, coreiface.ErrOffline - } else if err != nil { - return nil, err - } - - 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 -} - -// ParsePath parses path `p` using ipfspath parser, returns the parsed path. -func ParsePath(p string) (coreiface.Path, error) { - pp, err := ipfspath.ParsePath(p) - if err != nil { - return nil, err - } - return &path{path: pp}, nil -} - -// ParseCid parses the path from `c`, returns the parsed path. -func ParseCid(c *cid.Cid) coreiface.Path { - return &path{path: ipfspath.FromCid(c), cid: c, root: c} -} - -// ResolvePath parses path from string `p`, returns parsed path. -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/dag.go b/core/coreapi/dag.go index 9e9be2d9f4e..75416a249d0 100644 --- a/core/coreapi/dag.go +++ b/core/coreapi/dag.go @@ -20,7 +20,7 @@ type DagAPI CoreAPI // Put inserts data using specified format and input encoding. Unless used with // `WithCodes` or `WithHash`, the defaults "dag-cbor" and "sha256" are used. // Returns the path of the inserted data. -func (api *DagAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.DagPutOption) (coreiface.Path, error) { +func (api *DagAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.DagPutOption) (coreiface.ResolvedPath, error) { settings, err := caopts.DagPutOptions(opts...) if err != nil { return nil, err @@ -44,7 +44,7 @@ func (api *DagAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.DagPut return nil, err } - return ParseCid(nds[0].Cid()), nil + return coreiface.IpldPath(nds[0].Cid()), nil } // Get resolves `path` using Unixfs resolver, returns the resolved Node. @@ -66,7 +66,7 @@ func (api *DagAPI) Tree(ctx context.Context, p coreiface.Path, opts ...caopts.Da paths := n.Tree("", settings.Depth) out := make([]coreiface.Path, len(paths)) for n, p2 := range paths { - out[n], err = ParsePath(gopath.Join(p.String(), p2)) + out[n], err = coreiface.ParsePath(gopath.Join(p.String(), p2)) if err != nil { return nil, err } diff --git a/core/coreapi/dag_test.go b/core/coreapi/dag_test.go index ef7190104b0..773ff0f1a4c 100644 --- a/core/coreapi/dag_test.go +++ b/core/coreapi/dag_test.go @@ -6,11 +6,10 @@ import ( "strings" "testing" - coreapi "github.com/ipfs/go-ipfs/core/coreapi" + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + opt "github.com/ipfs/go-ipfs/core/coreapi/interface/options" mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash" - - opt "github.com/ipfs/go-ipfs/core/coreapi/interface/options" ) var ( @@ -74,7 +73,7 @@ func TestPath(t *testing.T) { t.Error(err) } - p, err := coreapi.ParsePath(path.Join(res.Cid().String(), "lnk")) + p, err := coreiface.ParsePath(path.Join(res.Cid().String(), "lnk")) if err != nil { t.Error(err) } diff --git a/core/coreapi/interface/block.go b/core/coreapi/interface/block.go index a9e577d7655..468c0094788 100644 --- a/core/coreapi/interface/block.go +++ b/core/coreapi/interface/block.go @@ -13,13 +13,13 @@ type BlockStat interface { Size() int // Path returns path to the block - Path() Path + Path() ResolvedPath } // BlockAPI specifies the interface to the block layer type BlockAPI interface { // Put imports raw block data, hashing it using specified settings. - Put(context.Context, io.Reader, ...options.BlockPutOption) (Path, error) + Put(context.Context, io.Reader, ...options.BlockPutOption) (ResolvedPath, error) // Get attempts to resolve the path and return a reader for data in the block Get(context.Context, Path) (io.Reader, error) diff --git a/core/coreapi/interface/coreapi.go b/core/coreapi/interface/coreapi.go index a77ad636720..82a2ebf4e32 100644 --- a/core/coreapi/interface/coreapi.go +++ b/core/coreapi/interface/coreapi.go @@ -32,7 +32,7 @@ type CoreAPI interface { Object() ObjectAPI // ResolvePath resolves the path using Unixfs resolver - ResolvePath(context.Context, Path) (Path, error) + ResolvePath(context.Context, Path) (ResolvedPath, error) // ResolveNode resolves the path (if not resolved already) using Unixfs // resolver, gets and returns the resolved Node diff --git a/core/coreapi/interface/dag.go b/core/coreapi/interface/dag.go index 158db7419d5..3f92ebab34c 100644 --- a/core/coreapi/interface/dag.go +++ b/core/coreapi/interface/dag.go @@ -14,7 +14,7 @@ type DagAPI interface { // Put inserts data using specified format and input encoding. // Unless used with WithCodec or WithHash, the defaults "dag-cbor" and // "sha256" are used. - Put(ctx context.Context, src io.Reader, opts ...options.DagPutOption) (Path, error) + Put(ctx context.Context, src io.Reader, opts ...options.DagPutOption) (ResolvedPath, error) // Get attempts to resolve and get the node specified by the path Get(ctx context.Context, path Path) (ipld.Node, error) diff --git a/core/coreapi/interface/object.go b/core/coreapi/interface/object.go index a18a38ebe5d..ea9aa59480b 100644 --- a/core/coreapi/interface/object.go +++ b/core/coreapi/interface/object.go @@ -38,7 +38,7 @@ type ObjectAPI interface { New(context.Context, ...options.ObjectNewOption) (ipld.Node, error) // Put imports the data into merkledag - Put(context.Context, io.Reader, ...options.ObjectPutOption) (Path, error) + Put(context.Context, io.Reader, ...options.ObjectPutOption) (ResolvedPath, error) // Get returns the node for the path Get(context.Context, Path) (ipld.Node, error) @@ -55,14 +55,14 @@ type ObjectAPI interface { // AddLink adds a link under the specified path. child path can point to a // subdirectory within the patent which must be present (can be overridden // with WithCreate option). - AddLink(ctx context.Context, base Path, name string, child Path, opts ...options.ObjectAddLinkOption) (Path, error) + AddLink(ctx context.Context, base Path, name string, child Path, opts ...options.ObjectAddLinkOption) (ResolvedPath, error) // RmLink removes a link from the node - RmLink(ctx context.Context, base Path, link string) (Path, error) + RmLink(ctx context.Context, base Path, link string) (ResolvedPath, error) // AppendData appends data to the node - AppendData(context.Context, Path, io.Reader) (Path, error) + AppendData(context.Context, Path, io.Reader) (ResolvedPath, error) // SetData sets the data contained in the node - SetData(context.Context, Path, io.Reader) (Path, error) + SetData(context.Context, Path, io.Reader) (ResolvedPath, error) } diff --git a/core/coreapi/interface/path.go b/core/coreapi/interface/path.go index 51513772fd4..e097ea3b692 100644 --- a/core/coreapi/interface/path.go +++ b/core/coreapi/interface/path.go @@ -1,18 +1,176 @@ package iface import ( + ipfspath "github.com/ipfs/go-ipfs/path" + cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" ) +//TODO: merge with ipfspath so we don't depend on it + // Path is a generic wrapper for paths used in the API. A path can be resolved // to a CID using one of Resolve functions in the API. +// +// Paths must be prefixed with a valid prefix: +// +// * /ipfs - Immutable unixfs path (files) +// * /ipld - Immutable ipld path (data) +// * /ipns - Mutable names. Usually resolves to one of the immutable paths +//TODO: /local (MFS) type Path interface { // String returns the path as a string. String() string - // Cid returns cid referred to by path + + // Namespace returns the first component of the path. + // + // For example path "/ipfs/QmHash", calling Namespace() will return "ipfs" + Namespace() string + + // Mutable returns false if the data pointed to by this path in guaranteed + // to not change. + // + // Note that resolved mutable path can be immutable. + Mutable() bool +} + +// ResolvedPath is a path which was resolved to the last resolvable node +type ResolvedPath interface { + // Cid returns the CID of the node referenced by the path. Remainder of the + // path is guaranteed to be within the node. + // + // Examples: + // If you have 3 linked objects: QmRoot -> A -> B: + // + // cidB := {"foo": {"bar": 42 }} + // cidA := {"B": {"/": cidB }} + // cidRoot := {"A": {"/": cidA }} + // + // And resolve paths: + // * "/ipfs/${cidRoot}" + // * Calling Cid() will return `cidRoot` + // * Calling Root() will return `cidRoot` + // * Calling Remainder() will return `` + // + // * "/ipfs/${cidRoot}/A" + // * Calling Cid() will return `cidA` + // * Calling Root() will return `cidRoot` + // * Calling Remainder() will return `` + // + // * "/ipfs/${cidRoot}/A/B/foo" + // * Calling Cid() will return `cidB` + // * Calling Root() will return `cidRoot` + // * Calling Remainder() will return `foo` + // + // * "/ipfs/${cidRoot}/A/B/foo/bar" + // * Calling Cid() will return `cidB` + // * Calling Root() will return `cidRoot` + // * Calling Remainder() will return `foo/bar` Cid() *cid.Cid - // Root returns cid of root path + + // Root returns the CID of the root object of the path + // + // Example: + // If you have 3 linked objects: QmRoot -> A -> B, and resolve path + // "/ipfs/QmRoot/A/B", the Root method will return the CID of object QmRoot + // + // For more examples see the documentation of Cid() method Root() *cid.Cid - // Resolved returns whether path has been fully resolved - Resolved() bool + + // Remainder returns unresolved part of the path + // + // Example: + // If you have 2 linked objects: QmRoot -> A, where A is a CBOR node + // containing the following data: + // + // {"foo": {"bar": 42 }} + // + // When resolving "/ipld/QmRoot/A/foo/bar", Remainder will return "foo/bar" + // + // For more examples see the documentation of Cid() method + Remainder() string + + Path +} + +// path implements coreiface.Path +type path struct { + path ipfspath.Path +} + +// resolvedPath implements coreiface.resolvedPath +type resolvedPath struct { + path + cid *cid.Cid + root *cid.Cid + remainder string +} + +// IpfsPath creates new /ipfs path from the provided CID +func IpfsPath(c *cid.Cid) ResolvedPath { + return &resolvedPath{ + path: path{ipfspath.Path("/ipfs/" + c.String())}, + cid: c, + root: c, + remainder: "", + } +} + +// IpldPath creates new /ipld path from the provided CID +func IpldPath(c *cid.Cid) ResolvedPath { + return &resolvedPath{ + path: path{ipfspath.Path("/ipld/" + c.String())}, + cid: c, + root: c, + remainder: "", + } +} + +// ParsePath parses string path to a Path +func ParsePath(p string) (Path, error) { + pp, err := ipfspath.ParsePath(p) + if err != nil { + return nil, err + } + + return &path{path: pp}, nil +} + +// NewResolvedPath creates new ResolvedPath. This function performs no checks +// and is intended to be used by resolver implementations. Incorrect inputs may +// cause panics. Handle with care. +func NewResolvedPath(ipath ipfspath.Path, c *cid.Cid, root *cid.Cid, remainder string) ResolvedPath { + return &resolvedPath{ + path: path{ipath}, + cid: c, + root: root, + remainder: remainder, + } +} + +func (p *path) String() string { + return p.path.String() +} + +func (p *path) Namespace() string { + if len(p.path.Segments()) < 1 { + panic("path without namespace") //this shouldn't happen under any scenario + } + return p.path.Segments()[0] +} + +func (p *path) Mutable() bool { + //TODO: MFS: check for /local + return p.Namespace() == "ipns" +} + +func (p *resolvedPath) Cid() *cid.Cid { + return p.cid +} + +func (p *resolvedPath) Root() *cid.Cid { + return p.root +} + +func (p *resolvedPath) Remainder() string { + return p.remainder } diff --git a/core/coreapi/interface/pin.go b/core/coreapi/interface/pin.go index 5994c758686..2e119cbeae8 100644 --- a/core/coreapi/interface/pin.go +++ b/core/coreapi/interface/pin.go @@ -9,7 +9,7 @@ import ( // Pin holds information about pinned resource type Pin interface { // Path to the pinned object - Path() Path + Path() ResolvedPath // Type of the pin Type() string @@ -27,7 +27,7 @@ type PinStatus interface { // BadPinNode is a node that has been marked as bad by Pin.Verify type BadPinNode interface { // Path is the path of the node - Path() Path + Path() ResolvedPath // Err is the reason why the node has been marked as bad Err() error diff --git a/core/coreapi/interface/unixfs.go b/core/coreapi/interface/unixfs.go index c59451d002f..1ddc2067444 100644 --- a/core/coreapi/interface/unixfs.go +++ b/core/coreapi/interface/unixfs.go @@ -10,7 +10,7 @@ import ( // UnixfsAPI is the basic interface to immutable files in IPFS type UnixfsAPI interface { // Add imports the data from the reader into merkledag file - Add(context.Context, io.Reader) (Path, error) + Add(context.Context, io.Reader) (ResolvedPath, error) // Cat returns a reader for the file Cat(context.Context, Path) (Reader, error) diff --git a/core/coreapi/key.go b/core/coreapi/key.go index 0198f6762b8..e22d70cb424 100644 --- a/core/coreapi/key.go +++ b/core/coreapi/key.go @@ -28,7 +28,12 @@ func (k *key) Name() string { // Path returns the path of the key. func (k *key) Path() coreiface.Path { - return &path{path: ipfspath.FromString(ipfspath.Join([]string{"/ipns", k.peerId}))} + path, err := coreiface.ParsePath(ipfspath.Join([]string{"/ipns", k.peerId})) + if err != nil { + panic("error parsing path: " + err.Error()) + } + + return path } // Generate generates new key, stores it in the keystore under the specified diff --git a/core/coreapi/name.go b/core/coreapi/name.go index 651d8552318..4e1018b5471 100644 --- a/core/coreapi/name.go +++ b/core/coreapi/name.go @@ -129,7 +129,7 @@ func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.Nam return nil, err } - return &path{path: output}, nil + return coreiface.ParsePath(output.String()) } func keylookup(n *core.IpfsNode, k string) (crypto.PrivKey, error) { diff --git a/core/coreapi/object.go b/core/coreapi/object.go index 5919a24330a..f0ccecef6fa 100644 --- a/core/coreapi/object.go +++ b/core/coreapi/object.go @@ -56,7 +56,7 @@ func (api *ObjectAPI) New(ctx context.Context, opts ...caopts.ObjectNewOption) ( return n, nil } -func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.ObjectPutOption) (coreiface.Path, error) { +func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.ObjectPutOption) (coreiface.ResolvedPath, error) { options, err := caopts.ObjectPutOptions(opts...) if err != nil { return nil, err @@ -121,7 +121,7 @@ func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.Obj return nil, err } - return ParseCid(dagnode.Cid()), nil + return coreiface.IpfsPath(dagnode.Cid()), nil } func (api *ObjectAPI) Get(ctx context.Context, path coreiface.Path) (ipld.Node, error) { @@ -180,7 +180,7 @@ func (api *ObjectAPI) Stat(ctx context.Context, path coreiface.Path) (*coreiface return out, nil } -func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name string, child coreiface.Path, opts ...caopts.ObjectAddLinkOption) (coreiface.Path, error) { +func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name string, child coreiface.Path, opts ...caopts.ObjectAddLinkOption) (coreiface.ResolvedPath, error) { options, err := caopts.ObjectAddLinkOptions(opts...) if err != nil { return nil, err @@ -218,10 +218,10 @@ func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name str return nil, err } - return ParseCid(nnode.Cid()), nil + return coreiface.IpfsPath(nnode.Cid()), nil } -func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link string) (coreiface.Path, error) { +func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link string) (coreiface.ResolvedPath, error) { baseNd, err := api.core().ResolveNode(ctx, base) if err != nil { return nil, err @@ -244,18 +244,18 @@ func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link stri return nil, err } - return ParseCid(nnode.Cid()), nil + return coreiface.IpfsPath(nnode.Cid()), nil } -func (api *ObjectAPI) AppendData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Path, error) { +func (api *ObjectAPI) AppendData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.ResolvedPath, error) { return api.patchData(ctx, path, r, true) } -func (api *ObjectAPI) SetData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.Path, error) { +func (api *ObjectAPI) SetData(ctx context.Context, path coreiface.Path, r io.Reader) (coreiface.ResolvedPath, error) { return api.patchData(ctx, path, r, false) } -func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.Reader, appendData bool) (coreiface.Path, error) { +func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.Reader, appendData bool) (coreiface.ResolvedPath, error) { nd, err := api.core().ResolveNode(ctx, path) if err != nil { return nil, err @@ -281,7 +281,7 @@ func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.R return nil, err } - return ParseCid(pbnd.Cid()), nil + return coreiface.IpfsPath(pbnd.Cid()), nil } func (api *ObjectAPI) core() coreiface.CoreAPI { diff --git a/core/coreapi/path.go b/core/coreapi/path.go new file mode 100644 index 00000000000..f2f578ed19d --- /dev/null +++ b/core/coreapi/path.go @@ -0,0 +1,84 @@ +package coreapi + +import ( + context "context" + fmt "fmt" + gopath "path" + + core "github.com/ipfs/go-ipfs/core" + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + namesys "github.com/ipfs/go-ipfs/namesys" + ipfspath "github.com/ipfs/go-ipfs/path" + resolver "github.com/ipfs/go-ipfs/path/resolver" + uio "github.com/ipfs/go-ipfs/unixfs/io" + + cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" + ipld "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" +) + +// ResolveNode resolves the path `p` using Unixfs resolver, gets and returns the +// resolved Node. +func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (ipld.Node, error) { + return resolveNode(ctx, api.node.DAG, api.node.Namesys, p) +} + +// ResolvePath resolves the path `p` using Unixfs resolver, returns the +// resolved path. +func (api *CoreAPI) ResolvePath(ctx context.Context, p coreiface.Path) (coreiface.ResolvedPath, error) { + return resolvePath(ctx, api.node.DAG, api.node.Namesys, p) +} + +func resolveNode(ctx context.Context, ng ipld.NodeGetter, nsys namesys.NameSystem, p coreiface.Path) (ipld.Node, error) { + rp, err := resolvePath(ctx, ng, nsys, p) + if err != nil { + return nil, err + } + + node, err := ng.Get(ctx, rp.Cid()) + if err != nil { + return nil, err + } + return node, nil +} + +func resolvePath(ctx context.Context, ng ipld.NodeGetter, nsys namesys.NameSystem, p coreiface.Path) (coreiface.ResolvedPath, error) { + if _, ok := p.(coreiface.ResolvedPath); ok { + return p.(coreiface.ResolvedPath), nil + } + + ipath := ipfspath.Path(p.String()) + ipath, err := core.ResolveIPNS(ctx, nsys, ipath) + if err == core.ErrNoNamesys { + return nil, coreiface.ErrOffline + } else if err != nil { + return nil, err + } + + var resolveOnce resolver.ResolveOnce + + switch ipath.Segments()[0] { + case "ipfs": + resolveOnce = uio.ResolveUnixfsOnce + case "ipld": + resolveOnce = resolver.ResolveSingle + default: + return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace()) + } + + r := &resolver.Resolver{ + DAG: ng, + ResolveOnce: resolveOnce, + } + + node, rest, err := r.ResolveToLastNode(ctx, ipath) + if err != nil { + return nil, err + } + + root, err := cid.Parse(ipath.Segments()[1]) + if err != nil { + return nil, err + } + + return coreiface.NewResolvedPath(ipath, node.Cid(), root, gopath.Join(rest...)), nil +} diff --git a/core/coreapi/path_test.go b/core/coreapi/path_test.go new file mode 100644 index 00000000000..76e78b5453a --- /dev/null +++ b/core/coreapi/path_test.go @@ -0,0 +1,154 @@ +package coreapi_test + +import ( + "context" + "strings" + "testing" + + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + "github.com/ipfs/go-ipfs/core/coreapi/interface/options" +) + +func TestMutablePath(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + // get self /ipns path + keys, err := api.Key().List(ctx) + if err != nil { + t.Fatal(err) + } + + if !keys[0].Path().Mutable() { + t.Error("expected self /ipns path to be mutable") + } + + blk, err := api.Block().Put(ctx, strings.NewReader(`foo`)) + if err != nil { + t.Error(err) + } + + if blk.Mutable() { + t.Error("expected /ipld path to be immutable") + } +} + +func TestPathRemainder(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"bar": "baz"}}`)) + if err != nil { + t.Fatal(err) + } + + p1, err := coreiface.ParsePath(obj.String() + "/foo/bar") + if err != nil { + t.Error(err) + } + + rp1, err := api.ResolvePath(ctx, p1) + if err != nil { + t.Fatal(err) + } + + if rp1.Remainder() != "foo/bar" { + t.Error("expected to get path remainder") + } +} + +func TestEmptyPathRemainder(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"bar": "baz"}}`)) + if err != nil { + t.Fatal(err) + } + + if obj.Remainder() != "" { + t.Error("expected the resolved path to not have a remainder") + } + + p1, err := coreiface.ParsePath(obj.String()) + if err != nil { + t.Error(err) + } + + rp1, err := api.ResolvePath(ctx, p1) + if err != nil { + t.Fatal(err) + } + + if rp1.Remainder() != "" { + t.Error("expected the resolved path to not have a remainder") + } +} + +func TestInvalidPathRemainder(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"bar": "baz"}}`)) + if err != nil { + t.Fatal(err) + } + + p1, err := coreiface.ParsePath(obj.String() + "/bar/baz") + if err != nil { + t.Error(err) + } + + _, err = api.ResolvePath(ctx, p1) + if err == nil || err.Error() != "no such link found" { + t.Fatalf("unexpected error: %s", err) + } +} + +func TestPathRoot(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + } + + blk, err := api.Block().Put(ctx, strings.NewReader(`foo`), options.Block.Format("raw")) + if err != nil { + t.Error(err) + } + + obj, err := api.Dag().Put(ctx, strings.NewReader(`{"foo": {"/": "`+blk.Cid().String()+`"}}`)) + if err != nil { + t.Fatal(err) + } + + p1, err := coreiface.ParsePath(obj.String() + "/foo") + if err != nil { + t.Error(err) + } + + rp, err := api.ResolvePath(ctx, p1) + if err != nil { + t.Fatal(err) + } + + if rp.Root().String() != obj.Cid().String() { + t.Error("unexpected path root") + } + + if rp.Cid().String() != blk.Cid().String() { + t.Error("unexpected path cid") + } +} diff --git a/core/coreapi/pin.go b/core/coreapi/pin.go index 13013f583c6..6c5a0fbdb71 100644 --- a/core/coreapi/pin.go +++ b/core/coreapi/pin.go @@ -9,11 +9,9 @@ import ( caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" corerepo "github.com/ipfs/go-ipfs/core/corerepo" merkledag "github.com/ipfs/go-ipfs/merkledag" - pin "github.com/ipfs/go-ipfs/pin" offline "gx/ipfs/QmShbyKV9P7QuFecDHXsgrQ4rxxm71MUkGVpwedT4VQ8Bf/go-ipfs-exchange-offline" cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" - ipld "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" ) type PinAPI CoreAPI @@ -26,7 +24,12 @@ func (api *PinAPI) Add(ctx context.Context, p coreiface.Path, opts ...caopts.Pin defer api.node.Blockstore.PinLock().Unlock() - _, err = corerepo.Pin(api.node, ctx, []string{p.String()}, settings.Recursive) + rp, err := api.core().ResolvePath(ctx, p) + if err != nil { + return err + } + + _, err = corerepo.Pin(api.node, ctx, []string{rp.Cid().String()}, settings.Recursive) if err != nil { return err } @@ -46,7 +49,7 @@ func (api *PinAPI) Ls(ctx context.Context, opts ...caopts.PinLsOption) ([]coreif return nil, fmt.Errorf("invalid type '%s', must be one of {direct, indirect, recursive, all}", settings.Type) } - return pinLsAll(settings.Type, ctx, api.node.Pinning, api.node.DAG) + return api.pinLsAll(settings.Type, ctx) } func (api *PinAPI) Rm(ctx context.Context, p coreiface.Path) error { @@ -64,7 +67,17 @@ func (api *PinAPI) Update(ctx context.Context, from coreiface.Path, to coreiface return err } - return api.node.Pinning.Update(ctx, from.Cid(), to.Cid(), settings.Unpin) + fp, err := api.core().ResolvePath(ctx, from) + if err != nil { + return err + } + + tp, err := api.core().ResolvePath(ctx, to) + if err != nil { + return err + } + + return api.node.Pinning.Update(ctx, fp.Cid(), tp.Cid(), settings.Unpin) } type pinStatus struct { @@ -75,8 +88,8 @@ type pinStatus struct { // BadNode is used in PinVerifyRes type badNode struct { - cid *cid.Cid - err error + path coreiface.ResolvedPath + err error } func (s *pinStatus) Ok() bool { @@ -87,8 +100,8 @@ func (s *pinStatus) BadNodes() []coreiface.BadPinNode { return s.badNodes } -func (n *badNode) Path() coreiface.Path { - return ParseCid(n.cid) +func (n *badNode) Path() coreiface.ResolvedPath { + return n.path } func (n *badNode) Err() error { @@ -112,7 +125,7 @@ func (api *PinAPI) Verify(ctx context.Context) (<-chan coreiface.PinStatus, erro links, err := getLinks(ctx, root) if err != nil { status := &pinStatus{ok: false, cid: root} - status.badNodes = []coreiface.BadPinNode{&badNode{cid: root, err: err}} + status.badNodes = []coreiface.BadPinNode{&badNode{path: coreiface.IpldPath(root), err: err}} visited[key] = status return status } @@ -143,18 +156,18 @@ func (api *PinAPI) Verify(ctx context.Context) (<-chan coreiface.PinStatus, erro type pinInfo struct { pinType string - object *cid.Cid + path coreiface.ResolvedPath } -func (p *pinInfo) Path() coreiface.Path { - return ParseCid(p.object) +func (p *pinInfo) Path() coreiface.ResolvedPath { + return p.path } func (p *pinInfo) Type() string { return p.pinType } -func pinLsAll(typeStr string, ctx context.Context, pinning pin.Pinner, dag ipld.DAGService) ([]coreiface.Pin, error) { +func (api *PinAPI) pinLsAll(typeStr string, ctx context.Context) ([]coreiface.Pin, error) { keys := make(map[string]*pinInfo) @@ -162,18 +175,18 @@ func pinLsAll(typeStr string, ctx context.Context, pinning pin.Pinner, dag ipld. for _, c := range keyList { keys[c.String()] = &pinInfo{ pinType: typeStr, - object: c, + path: coreiface.IpldPath(c), } } } if typeStr == "direct" || typeStr == "all" { - AddToResultKeys(pinning.DirectKeys(), "direct") + AddToResultKeys(api.node.Pinning.DirectKeys(), "direct") } if typeStr == "indirect" || typeStr == "all" { set := cid.NewSet() - for _, k := range pinning.RecursiveKeys() { - err := merkledag.EnumerateChildren(ctx, merkledag.GetLinksWithDAG(dag), k, set.Visit) + for _, k := range api.node.Pinning.RecursiveKeys() { + err := merkledag.EnumerateChildren(ctx, merkledag.GetLinksWithDAG(api.node.DAG), k, set.Visit) if err != nil { return nil, err } @@ -181,7 +194,7 @@ func pinLsAll(typeStr string, ctx context.Context, pinning pin.Pinner, dag ipld. AddToResultKeys(set.Keys(), "indirect") } if typeStr == "recursive" || typeStr == "all" { - AddToResultKeys(pinning.RecursiveKeys(), "recursive") + AddToResultKeys(api.node.Pinning.RecursiveKeys(), "recursive") } out := make([]coreiface.Pin, 0, len(keys)) @@ -191,3 +204,7 @@ func pinLsAll(typeStr string, ctx context.Context, pinning pin.Pinner, dag ipld. return out, nil } + +func (api *PinAPI) core() coreiface.CoreAPI { + return (*CoreAPI)(api) +} diff --git a/core/coreapi/pin_test.go b/core/coreapi/pin_test.go index 936bae3bd40..9bbf16c9cfa 100644 --- a/core/coreapi/pin_test.go +++ b/core/coreapi/pin_test.go @@ -52,7 +52,7 @@ func TestPinSimple(t *testing.T) { t.Errorf("unexpected pin list len: %d", len(list)) } - if list[0].Path().String() != p.String() { + if list[0].Path().Cid().String() != p.Cid().String() { t.Error("paths don't match") } @@ -156,7 +156,7 @@ func TestPinRecursive(t *testing.T) { t.Errorf("unexpected pin list len: %d", len(list)) } - if list[0].Path().String() != p0.String() { + if list[0].Path().Cid().String() != p0.Cid().String() { t.Error("unexpected path") } @@ -195,7 +195,7 @@ func TestPinRecursive(t *testing.T) { t.Fatalf("unexpected badNodes len") } - if r.BadNodes()[0].Path().String() != p0.String() { + if r.BadNodes()[0].Path().Cid().String() != p0.Cid().String() { t.Error("unexpected badNode path") } diff --git a/core/coreapi/unixfs.go b/core/coreapi/unixfs.go index 51b58404cd8..9cc16ddc596 100644 --- a/core/coreapi/unixfs.go +++ b/core/coreapi/unixfs.go @@ -16,7 +16,7 @@ type UnixfsAPI CoreAPI // Add builds a merkledag node from a reader, adds it to the blockstore, // and returns the key representing that node. -func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (coreiface.Path, error) { +func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (coreiface.ResolvedPath, error) { k, err := coreunix.AddWithContext(ctx, api.node, r) if err != nil { return nil, err @@ -25,7 +25,7 @@ func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (coreiface.Path, err if err != nil { return nil, err } - return ParseCid(c), nil + return coreiface.IpfsPath(c), nil } // Cat returns the data contained by an IPFS or IPNS object(s) at path `p`. diff --git a/core/coreapi/unixfs_test.go b/core/coreapi/unixfs_test.go index c8ea4bc59b8..2e7f15f62eb 100644 --- a/core/coreapi/unixfs_test.go +++ b/core/coreapi/unixfs_test.go @@ -12,6 +12,7 @@ import ( core "github.com/ipfs/go-ipfs/core" coreapi "github.com/ipfs/go-ipfs/core/coreapi" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + options "github.com/ipfs/go-ipfs/core/coreapi/interface/options" coreunix "github.com/ipfs/go-ipfs/core/coreunix" keystore "github.com/ipfs/go-ipfs/keystore" mdag "github.com/ipfs/go-ipfs/merkledag" @@ -29,14 +30,11 @@ import ( const testPeerID = "QmTFauExutTsy4XP6JbMFcw2Wa9645HJt2bTqL6qYDCKfe" // `echo -n 'hello, world!' | ipfs add` -var hello = coreapi.ResolvedPath("/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk", nil, nil) +var hello = "/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk" var helloStr = "hello, world!" -// `ipfs object new unixfs-dir` -var emptyDir = coreapi.ResolvedPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn", nil, nil) - // `echo -n | ipfs add` -var emptyFile = coreapi.ResolvedPath("/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", nil, nil) +var emptyFile = "/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH" func makeAPIIdent(ctx context.Context, fullIdentity bool) (*core.IpfsNode, coreiface.CoreAPI, error) { var ident config.Identity @@ -98,11 +96,11 @@ func TestAdd(t *testing.T) { t.Error(err) } - if p.String() != hello.String() { + if p.String() != hello { t.Fatalf("expected path %s, got: %s", hello, p) } - r, err := api.Unixfs().Cat(ctx, hello) + r, err := api.Unixfs().Cat(ctx, p) if err != nil { t.Fatal(err) } @@ -130,7 +128,7 @@ func TestAddEmptyFile(t *testing.T) { t.Error(err) } - if p.String() != emptyFile.String() { + if p.String() != emptyFile { t.Fatalf("expected path %s, got: %s", hello, p) } } @@ -149,11 +147,16 @@ func TestCatBasic(t *testing.T) { } p = "/ipfs/" + p - if p != hello.String() { + if p != hello { t.Fatalf("expected CID %s, got: %s", hello, p) } - r, err := api.Unixfs().Cat(ctx, hello) + helloPath, err := coreiface.ParsePath(hello) + if err != nil { + t.Fatal(err) + } + + r, err := api.Unixfs().Cat(ctx, helloPath) if err != nil { t.Fatal(err) } @@ -180,7 +183,12 @@ func TestCatEmptyFile(t *testing.T) { t.Fatal(err) } - r, err := api.Unixfs().Cat(ctx, emptyFile) + emptyFilePath, err := coreiface.ParsePath(emptyFile) + if err != nil { + t.Fatal(err) + } + + r, err := api.Unixfs().Cat(ctx, emptyFilePath) if err != nil { t.Fatal(err) } @@ -206,13 +214,18 @@ func TestCatDir(t *testing.T) { if err != nil { t.Error(err) } - p := coreapi.ParseCid(edir.Cid()) + p := coreiface.IpfsPath(edir.Cid()) - if p.String() != emptyDir.String() { - t.Fatalf("expected path %s, got: %s", emptyDir, p) + emptyDir, err := api.Object().New(ctx, options.Object.Type("unixfs-dir")) + if err != nil { + t.Error(err) } - _, err = api.Unixfs().Cat(ctx, emptyDir) + if p.String() != coreiface.IpfsPath(emptyDir.Cid()).String() { + t.Fatalf("expected path %s, got: %s", emptyDir.Cid(), p.String()) + } + + _, err = api.Unixfs().Cat(ctx, coreiface.IpfsPath(emptyDir.Cid())) if err != coreiface.ErrIsDir { t.Fatalf("expected ErrIsDir, got: %s", err) } @@ -231,7 +244,7 @@ func TestCatNonUnixfs(t *testing.T) { t.Error(err) } - _, err = api.Unixfs().Cat(ctx, coreapi.ParseCid(nd.Cid())) + _, err = api.Unixfs().Cat(ctx, coreiface.IpfsPath(nd.Cid())) if !strings.Contains(err.Error(), "proto: required field") { t.Fatalf("expected protobuf error, got: %s", err) } @@ -244,7 +257,11 @@ func TestCatOffline(t *testing.T) { t.Error(err) } - _, err = api.Unixfs().Cat(ctx, coreapi.ResolvedPath("/ipns/Qmfoobar", nil, nil)) + p, err := coreiface.ParsePath("/ipns/Qmfoobar") + if err != nil { + t.Error(err) + } + _, err = api.Unixfs().Cat(ctx, p) if err != coreiface.ErrOffline { t.Fatalf("expected ErrOffline, got: %s", err) } @@ -266,7 +283,10 @@ func TestLs(t *testing.T) { if len(parts) != 2 { t.Errorf("unexpected path: %s", k) } - p := coreapi.ResolvedPath("/ipfs/"+parts[0], nil, nil) + p, err := coreiface.ParsePath("/ipfs/" + parts[0]) + if err != nil { + t.Error(err) + } links, err := api.Unixfs().Ls(ctx, p) if err != nil { @@ -299,7 +319,12 @@ func TestLsEmptyDir(t *testing.T) { t.Error(err) } - links, err := api.Unixfs().Ls(ctx, emptyDir) + emptyDir, err := api.Object().New(ctx, options.Object.Type("unixfs-dir")) + if err != nil { + t.Error(err) + } + + links, err := api.Unixfs().Ls(ctx, coreiface.IpfsPath(emptyDir.Cid())) if err != nil { t.Error(err) } @@ -327,7 +352,7 @@ func TestLsNonUnixfs(t *testing.T) { t.Error(err) } - links, err := api.Unixfs().Ls(ctx, coreapi.ParseCid(nd.Cid())) + links, err := api.Unixfs().Ls(ctx, coreiface.IpfsPath(nd.Cid())) if err != nil { t.Error(err) } diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index a53c71f2a19..1236b30f576 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -13,7 +13,6 @@ 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" dag "github.com/ipfs/go-ipfs/merkledag" @@ -160,7 +159,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr ipnsHostname = true } - parsedPath, err := coreapi.ParsePath(urlPath) + parsedPath, err := coreiface.ParsePath(urlPath) if err != nil { webError(w, "invalid ipfs path", err, http.StatusBadRequest) return @@ -288,7 +287,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr return } - dr, err := i.api.Unixfs().Cat(ctx, coreapi.ParseCid(ixnd.Cid())) + dr, err := i.api.Unixfs().Cat(ctx, coreiface.IpfsPath(ixnd.Cid())) if err != nil { internalWebError(w, err) return diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index 900020cbab6..e8ac707837b 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -171,7 +171,7 @@ func TestGatewayGet(t *testing.T) { {"working.example.com", "/", http.StatusOK, "fnord"}, {"double.example.com", "/", http.StatusOK, "fnord"}, {"triple.example.com", "/", http.StatusOK, "fnord"}, - {"working.example.com", "/ipfs/" + k, http.StatusNotFound, "ipfs resolve -r /ipns/working.example.com/ipfs/" + k + ": no link named \"ipfs\" under " + k + "\n"}, + {"working.example.com", "/ipfs/" + k, http.StatusNotFound, "ipfs resolve -r /ipns/working.example.com/ipfs/" + k + ": no link by that name\n"}, {"broken.example.com", "/", http.StatusNotFound, "ipfs resolve -r /ipns/broken.example.com/: " + namesys.ErrResolveFailed.Error() + "\n"}, {"broken.example.com", "/ipfs/" + k, http.StatusNotFound, "ipfs resolve -r /ipns/broken.example.com/ipfs/" + k + ": " + namesys.ErrResolveFailed.Error() + "\n"}, } { diff --git a/core/pathresolver.go b/core/pathresolver.go index ea5ea7d9c1b..7fbb47d6a7f 100644 --- a/core/pathresolver.go +++ b/core/pathresolver.go @@ -19,10 +19,8 @@ import ( var ErrNoNamesys = errors.New( "core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") -// Resolve resolves the given path by parsing out protocol-specific -// entries (e.g. /ipns/) and then going through the /ipfs/ -// entries and returning the final node. -func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, p path.Path) (ipld.Node, error) { +// ResolveIPNS resolves /ipns paths +func ResolveIPNS(ctx context.Context, nsys namesys.NameSystem, p path.Path) (path.Path, error) { if strings.HasPrefix(p.String(), "/ipns/") { evt := log.EventBegin(ctx, "resolveIpnsPath") defer evt.Done() @@ -31,36 +29,47 @@ func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, // TODO(cryptix): we should be able to query the local cache for the path if nsys == nil { evt.Append(logging.LoggableMap{"error": ErrNoNamesys.Error()}) - return nil, ErrNoNamesys + return "", ErrNoNamesys } seg := p.Segments() if len(seg) < 2 || seg[1] == "" { // just "/" without further segments evt.Append(logging.LoggableMap{"error": path.ErrNoComponents.Error()}) - return nil, path.ErrNoComponents + return "", path.ErrNoComponents } extensions := seg[2:] resolvable, err := path.FromSegments("/", seg[0], seg[1]) if err != nil { evt.Append(logging.LoggableMap{"error": err.Error()}) - return nil, err + return "", err } respath, err := nsys.Resolve(ctx, resolvable.String()) if err != nil { evt.Append(logging.LoggableMap{"error": err.Error()}) - return nil, err + return "", err } segments := append(respath.Segments(), extensions...) p, err = path.FromSegments("/", segments...) if err != nil { evt.Append(logging.LoggableMap{"error": err.Error()}) - return nil, err + return "", err } } + return p, nil +} + +// Resolve resolves the given path by parsing out protocol-specific +// entries (e.g. /ipns/) and then going through the /ipfs/ +// entries and returning the final node. +func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, p path.Path) (ipld.Node, error) { + p, err := ResolveIPNS(ctx, nsys, p) + if err != nil { + return nil, err + } // ok, we have an IPFS path now (or what we'll treat as one) return r.ResolvePath(ctx, p) diff --git a/path/path.go b/path/path.go index 5622a6d0517..1608b6ca3c0 100644 --- a/path/path.go +++ b/path/path.go @@ -59,10 +59,11 @@ func (p Path) String() string { return string(p) } -// IsJustAKey returns true if the path is of the form or /ipfs/. +// IsJustAKey returns true if the path is of the form or /ipfs/, or +// /ipld/ func (p Path) IsJustAKey() bool { parts := p.Segments() - return len(parts) == 2 && parts[0] == "ipfs" + return len(parts) == 2 && (parts[0] == "ipfs" || parts[0] == "ipld") } // PopLastSegment returns a new Path without its final segment, and the final @@ -120,7 +121,7 @@ func ParsePath(txt string) (Path, error) { if _, err := ParseCidToPath(parts[2]); err != nil { return "", err } - } else if parts[1] != "ipns" { + } else if parts[1] != "ipns" && parts[1] != "ipld" { //TODO: make this smarter return "", ErrBadPath } @@ -161,7 +162,7 @@ func SplitList(pth string) []string { // must be a Multihash) and return it separately. func SplitAbsPath(fpath Path) (*cid.Cid, []string, error) { parts := fpath.Segments() - if parts[0] == "ipfs" { + if parts[0] == "ipfs" || parts[0] == "ipld" { parts = parts[1:] } diff --git a/path/path_test.go b/path/path_test.go index b095ffd98e7..db28193c8da 100644 --- a/path/path_test.go +++ b/path/path_test.go @@ -9,6 +9,9 @@ func TestPathParsing(t *testing.T) { "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": true, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b/c/d/e/f": true, + "/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true, + "/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": true, + "/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b/c/d/e/f": true, "/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b/c/d/e/f": true, "/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true, "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b/c/d/e/f": true, @@ -36,6 +39,8 @@ func TestIsJustAKey(t *testing.T) { "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": false, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": false, "/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": false, + "/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": false, + "/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true, } for p, expected := range cases { @@ -57,6 +62,7 @@ func TestPopLastSegment(t *testing.T) { "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": []string{"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n", "a"}, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": []string{"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a", "b"}, "/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/x/y/z": []string{"/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/x/y", "z"}, + "/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/x/y/z": []string{"/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/x/y", "z"}, } for p, expected := range cases { diff --git a/path/resolver/resolver.go b/path/resolver/resolver.go index 8c7b28b1c0c..05341655a33 100644 --- a/path/resolver/resolver.go +++ b/path/resolver/resolver.go @@ -34,6 +34,9 @@ func (e ErrNoLink) Error() string { return fmt.Sprintf("no link named %q under %s", e.Name, e.Node.String()) } +// ResolveOnce resolves path through a single node +type ResolveOnce func(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) + // Resolver provides path resolution to IPFS // It has a pointer to a DAGService, which is uses to resolve nodes. // TODO: now that this is more modular, try to unify this code with the @@ -41,7 +44,7 @@ func (e ErrNoLink) Error() string { type Resolver struct { DAG ipld.NodeGetter - ResolveOnce func(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) + ResolveOnce ResolveOnce } // NewBasicResolver constructs a new basic resolver.