1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-26 23:53:19 +08:00

Merge pull request #4672 from ipfs/feat/coreapi-paths

Improve paths in CoreApi
This commit is contained in:
Whyrusleeping
2018-07-17 18:06:58 -05:00
committed by GitHub
26 changed files with 586 additions and 199 deletions

View File

@ -20,11 +20,11 @@ import (
type BlockAPI CoreAPI type BlockAPI CoreAPI
type BlockStat struct { type BlockStat struct {
path coreiface.Path path coreiface.ResolvedPath
size int 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...) settings, err := caopts.BlockPutOptions(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -65,11 +65,16 @@ func (api *BlockAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.Bloc
return nil, err 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) { 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 { if err != nil {
return nil, err 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 { 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...) settings, err := caopts.BlockRmOptions(opts...)
if err != nil { if err != nil {
return err return err
} }
cids := []*cid.Cid{p.Cid()} cids := []*cid.Cid{rp.Cid()}
o := util.RmBlocksOpts{Force: settings.Force} o := util.RmBlocksOpts{Force: settings.Force}
out, err := util.RmBlocks(api.node.Blockstore, api.node.Pinning, cids, o) 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) { 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 { if err != nil {
return nil, err return nil, err
} }
return &BlockStat{ return &BlockStat{
path: ParseCid(b.Cid()), path: coreiface.IpldPath(b.Cid()),
size: len(b.RawData()), size: len(b.RawData()),
}, nil }, nil
} }
@ -126,6 +141,10 @@ func (bs *BlockStat) Size() int {
return bs.size return bs.size
} }
func (bs *BlockStat) Path() coreiface.Path { func (bs *BlockStat) Path() coreiface.ResolvedPath {
return bs.path return bs.path
} }
func (api *BlockAPI) core() coreiface.CoreAPI {
return (*CoreAPI)(api)
}

View File

@ -14,17 +14,8 @@ Interfaces here aren't yet completely stable.
package coreapi package coreapi
import ( import (
"context"
core "github.com/ipfs/go-ipfs/core" core "github.com/ipfs/go-ipfs/core"
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" 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 { type CoreAPI struct {
@ -71,86 +62,3 @@ func (api *CoreAPI) Object() coreiface.ObjectAPI {
func (api *CoreAPI) Pin() coreiface.PinAPI { func (api *CoreAPI) Pin() coreiface.PinAPI {
return (*PinAPI)(api) 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 }

View File

@ -20,7 +20,7 @@ type DagAPI CoreAPI
// Put inserts data using specified format and input encoding. Unless used with // Put inserts data using specified format and input encoding. Unless used with
// `WithCodes` or `WithHash`, the defaults "dag-cbor" and "sha256" are used. // `WithCodes` or `WithHash`, the defaults "dag-cbor" and "sha256" are used.
// Returns the path of the inserted data. // 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...) settings, err := caopts.DagPutOptions(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -44,7 +44,7 @@ func (api *DagAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.DagPut
return nil, err 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. // 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) paths := n.Tree("", settings.Depth)
out := make([]coreiface.Path, len(paths)) out := make([]coreiface.Path, len(paths))
for n, p2 := range 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,11 +6,10 @@ import (
"strings" "strings"
"testing" "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" mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash"
opt "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
) )
var ( var (
@ -74,7 +73,7 @@ func TestPath(t *testing.T) {
t.Error(err) 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 { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -13,13 +13,13 @@ type BlockStat interface {
Size() int Size() int
// Path returns path to the block // Path returns path to the block
Path() Path Path() ResolvedPath
} }
// BlockAPI specifies the interface to the block layer // BlockAPI specifies the interface to the block layer
type BlockAPI interface { type BlockAPI interface {
// Put imports raw block data, hashing it using specified settings. // 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 attempts to resolve the path and return a reader for data in the block
Get(context.Context, Path) (io.Reader, error) Get(context.Context, Path) (io.Reader, error)

View File

@ -32,7 +32,7 @@ type CoreAPI interface {
Object() ObjectAPI Object() ObjectAPI
// ResolvePath resolves the path using Unixfs resolver // 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 // ResolveNode resolves the path (if not resolved already) using Unixfs
// resolver, gets and returns the resolved Node // resolver, gets and returns the resolved Node

View File

@ -14,7 +14,7 @@ type DagAPI interface {
// Put inserts data using specified format and input encoding. // Put inserts data using specified format and input encoding.
// Unless used with WithCodec or WithHash, the defaults "dag-cbor" and // Unless used with WithCodec or WithHash, the defaults "dag-cbor" and
// "sha256" are used. // "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 attempts to resolve and get the node specified by the path
Get(ctx context.Context, path Path) (ipld.Node, error) Get(ctx context.Context, path Path) (ipld.Node, error)

View File

@ -38,7 +38,7 @@ type ObjectAPI interface {
New(context.Context, ...options.ObjectNewOption) (ipld.Node, error) New(context.Context, ...options.ObjectNewOption) (ipld.Node, error)
// Put imports the data into merkledag // 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 returns the node for the path
Get(context.Context, Path) (ipld.Node, error) 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 // 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 // subdirectory within the patent which must be present (can be overridden
// with WithCreate option). // 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 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 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 sets the data contained in the node
SetData(context.Context, Path, io.Reader) (Path, error) SetData(context.Context, Path, io.Reader) (ResolvedPath, error)
} }

View File

@ -1,18 +1,176 @@
package iface package iface
import ( import (
ipfspath "github.com/ipfs/go-ipfs/path"
cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" 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 // 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. // 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 { type Path interface {
// String returns the path as a string. // String returns the path as a string.
String() string String() string
// Cid returns cid referred to by path
Cid() *cid.Cid // Namespace returns the first component of the path.
// Root returns cid of root path //
Root() *cid.Cid // For example path "/ipfs/QmHash", calling Namespace() will return "ipfs"
// Resolved returns whether path has been fully resolved Namespace() string
Resolved() bool
// 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 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
// 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
} }

View File

@ -9,7 +9,7 @@ import (
// Pin holds information about pinned resource // Pin holds information about pinned resource
type Pin interface { type Pin interface {
// Path to the pinned object // Path to the pinned object
Path() Path Path() ResolvedPath
// Type of the pin // Type of the pin
Type() string Type() string
@ -27,7 +27,7 @@ type PinStatus interface {
// BadPinNode is a node that has been marked as bad by Pin.Verify // BadPinNode is a node that has been marked as bad by Pin.Verify
type BadPinNode interface { type BadPinNode interface {
// Path is the path of the node // Path is the path of the node
Path() Path Path() ResolvedPath
// Err is the reason why the node has been marked as bad // Err is the reason why the node has been marked as bad
Err() error Err() error

View File

@ -10,7 +10,7 @@ import (
// UnixfsAPI is the basic interface to immutable files in IPFS // UnixfsAPI is the basic interface to immutable files in IPFS
type UnixfsAPI interface { type UnixfsAPI interface {
// Add imports the data from the reader into merkledag file // 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 returns a reader for the file
Cat(context.Context, Path) (Reader, error) Cat(context.Context, Path) (Reader, error)

View File

@ -28,7 +28,12 @@ func (k *key) Name() string {
// Path returns the path of the key. // Path returns the path of the key.
func (k *key) Path() coreiface.Path { 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 // Generate generates new key, stores it in the keystore under the specified

View File

@ -129,7 +129,7 @@ func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.Nam
return nil, err return nil, err
} }
return &path{path: output}, nil return coreiface.ParsePath(output.String())
} }
func keylookup(n *core.IpfsNode, k string) (crypto.PrivKey, error) { func keylookup(n *core.IpfsNode, k string) (crypto.PrivKey, error) {

View File

@ -56,7 +56,7 @@ func (api *ObjectAPI) New(ctx context.Context, opts ...caopts.ObjectNewOption) (
return n, nil 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...) options, err := caopts.ObjectPutOptions(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -121,7 +121,7 @@ func (api *ObjectAPI) Put(ctx context.Context, src io.Reader, opts ...caopts.Obj
return nil, err 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) { 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 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...) options, err := caopts.ObjectAddLinkOptions(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -218,10 +218,10 @@ func (api *ObjectAPI) AddLink(ctx context.Context, base coreiface.Path, name str
return nil, err 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) baseNd, err := api.core().ResolveNode(ctx, base)
if err != nil { if err != nil {
return nil, err return nil, err
@ -244,18 +244,18 @@ func (api *ObjectAPI) RmLink(ctx context.Context, base coreiface.Path, link stri
return nil, err 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) 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) 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) nd, err := api.core().ResolveNode(ctx, path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -281,7 +281,7 @@ func (api *ObjectAPI) patchData(ctx context.Context, path coreiface.Path, r io.R
return nil, err return nil, err
} }
return ParseCid(pbnd.Cid()), nil return coreiface.IpfsPath(pbnd.Cid()), nil
} }
func (api *ObjectAPI) core() coreiface.CoreAPI { func (api *ObjectAPI) core() coreiface.CoreAPI {

84
core/coreapi/path.go Normal file
View File

@ -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
}

154
core/coreapi/path_test.go Normal file
View File

@ -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")
}
}

View File

@ -9,11 +9,9 @@ import (
caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options"
corerepo "github.com/ipfs/go-ipfs/core/corerepo" corerepo "github.com/ipfs/go-ipfs/core/corerepo"
merkledag "github.com/ipfs/go-ipfs/merkledag" merkledag "github.com/ipfs/go-ipfs/merkledag"
pin "github.com/ipfs/go-ipfs/pin"
offline "gx/ipfs/QmShbyKV9P7QuFecDHXsgrQ4rxxm71MUkGVpwedT4VQ8Bf/go-ipfs-exchange-offline" offline "gx/ipfs/QmShbyKV9P7QuFecDHXsgrQ4rxxm71MUkGVpwedT4VQ8Bf/go-ipfs-exchange-offline"
cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid"
ipld "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format"
) )
type PinAPI CoreAPI 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() 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 { if err != nil {
return err 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 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 { 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 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 { type pinStatus struct {
@ -75,8 +88,8 @@ type pinStatus struct {
// BadNode is used in PinVerifyRes // BadNode is used in PinVerifyRes
type badNode struct { type badNode struct {
cid *cid.Cid path coreiface.ResolvedPath
err error err error
} }
func (s *pinStatus) Ok() bool { func (s *pinStatus) Ok() bool {
@ -87,8 +100,8 @@ func (s *pinStatus) BadNodes() []coreiface.BadPinNode {
return s.badNodes return s.badNodes
} }
func (n *badNode) Path() coreiface.Path { func (n *badNode) Path() coreiface.ResolvedPath {
return ParseCid(n.cid) return n.path
} }
func (n *badNode) Err() error { 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) links, err := getLinks(ctx, root)
if err != nil { if err != nil {
status := &pinStatus{ok: false, cid: root} 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 visited[key] = status
return status return status
} }
@ -143,18 +156,18 @@ func (api *PinAPI) Verify(ctx context.Context) (<-chan coreiface.PinStatus, erro
type pinInfo struct { type pinInfo struct {
pinType string pinType string
object *cid.Cid path coreiface.ResolvedPath
} }
func (p *pinInfo) Path() coreiface.Path { func (p *pinInfo) Path() coreiface.ResolvedPath {
return ParseCid(p.object) return p.path
} }
func (p *pinInfo) Type() string { func (p *pinInfo) Type() string {
return p.pinType 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) 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 { for _, c := range keyList {
keys[c.String()] = &pinInfo{ keys[c.String()] = &pinInfo{
pinType: typeStr, pinType: typeStr,
object: c, path: coreiface.IpldPath(c),
} }
} }
} }
if typeStr == "direct" || typeStr == "all" { if typeStr == "direct" || typeStr == "all" {
AddToResultKeys(pinning.DirectKeys(), "direct") AddToResultKeys(api.node.Pinning.DirectKeys(), "direct")
} }
if typeStr == "indirect" || typeStr == "all" { if typeStr == "indirect" || typeStr == "all" {
set := cid.NewSet() set := cid.NewSet()
for _, k := range pinning.RecursiveKeys() { for _, k := range api.node.Pinning.RecursiveKeys() {
err := merkledag.EnumerateChildren(ctx, merkledag.GetLinksWithDAG(dag), k, set.Visit) err := merkledag.EnumerateChildren(ctx, merkledag.GetLinksWithDAG(api.node.DAG), k, set.Visit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -181,7 +194,7 @@ func pinLsAll(typeStr string, ctx context.Context, pinning pin.Pinner, dag ipld.
AddToResultKeys(set.Keys(), "indirect") AddToResultKeys(set.Keys(), "indirect")
} }
if typeStr == "recursive" || typeStr == "all" { if typeStr == "recursive" || typeStr == "all" {
AddToResultKeys(pinning.RecursiveKeys(), "recursive") AddToResultKeys(api.node.Pinning.RecursiveKeys(), "recursive")
} }
out := make([]coreiface.Pin, 0, len(keys)) 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 return out, nil
} }
func (api *PinAPI) core() coreiface.CoreAPI {
return (*CoreAPI)(api)
}

View File

@ -52,7 +52,7 @@ func TestPinSimple(t *testing.T) {
t.Errorf("unexpected pin list len: %d", len(list)) 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") t.Error("paths don't match")
} }
@ -156,7 +156,7 @@ func TestPinRecursive(t *testing.T) {
t.Errorf("unexpected pin list len: %d", len(list)) 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") t.Error("unexpected path")
} }
@ -195,7 +195,7 @@ func TestPinRecursive(t *testing.T) {
t.Fatalf("unexpected badNodes len") 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") t.Error("unexpected badNode path")
} }

View File

@ -16,7 +16,7 @@ type UnixfsAPI CoreAPI
// Add builds a merkledag node from a reader, adds it to the blockstore, // Add builds a merkledag node from a reader, adds it to the blockstore,
// and returns the key representing that node. // 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) k, err := coreunix.AddWithContext(ctx, api.node, r)
if err != nil { if err != nil {
return nil, err return nil, err
@ -25,7 +25,7 @@ func (api *UnixfsAPI) Add(ctx context.Context, r io.Reader) (coreiface.Path, err
if err != nil { if err != nil {
return nil, err 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`. // Cat returns the data contained by an IPFS or IPNS object(s) at path `p`.

View File

@ -12,6 +12,7 @@ import (
core "github.com/ipfs/go-ipfs/core" core "github.com/ipfs/go-ipfs/core"
coreapi "github.com/ipfs/go-ipfs/core/coreapi" coreapi "github.com/ipfs/go-ipfs/core/coreapi"
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" 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" coreunix "github.com/ipfs/go-ipfs/core/coreunix"
keystore "github.com/ipfs/go-ipfs/keystore" keystore "github.com/ipfs/go-ipfs/keystore"
mdag "github.com/ipfs/go-ipfs/merkledag" mdag "github.com/ipfs/go-ipfs/merkledag"
@ -29,14 +30,11 @@ import (
const testPeerID = "QmTFauExutTsy4XP6JbMFcw2Wa9645HJt2bTqL6qYDCKfe" const testPeerID = "QmTFauExutTsy4XP6JbMFcw2Wa9645HJt2bTqL6qYDCKfe"
// `echo -n 'hello, world!' | ipfs add` // `echo -n 'hello, world!' | ipfs add`
var hello = coreapi.ResolvedPath("/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk", nil, nil) var hello = "/ipfs/QmQy2Dw4Wk7rdJKjThjYXzfFJNaRKRHhHP5gHHXroJMYxk"
var helloStr = "hello, world!" var helloStr = "hello, world!"
// `ipfs object new unixfs-dir`
var emptyDir = coreapi.ResolvedPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn", nil, nil)
// `echo -n | ipfs add` // `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) { func makeAPIIdent(ctx context.Context, fullIdentity bool) (*core.IpfsNode, coreiface.CoreAPI, error) {
var ident config.Identity var ident config.Identity
@ -98,11 +96,11 @@ func TestAdd(t *testing.T) {
t.Error(err) t.Error(err)
} }
if p.String() != hello.String() { if p.String() != hello {
t.Fatalf("expected path %s, got: %s", hello, p) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -130,7 +128,7 @@ func TestAddEmptyFile(t *testing.T) {
t.Error(err) t.Error(err)
} }
if p.String() != emptyFile.String() { if p.String() != emptyFile {
t.Fatalf("expected path %s, got: %s", hello, p) t.Fatalf("expected path %s, got: %s", hello, p)
} }
} }
@ -149,11 +147,16 @@ func TestCatBasic(t *testing.T) {
} }
p = "/ipfs/" + p p = "/ipfs/" + p
if p != hello.String() { if p != hello {
t.Fatalf("expected CID %s, got: %s", hello, p) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -180,7 +183,12 @@ func TestCatEmptyFile(t *testing.T) {
t.Fatal(err) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -206,13 +214,18 @@ func TestCatDir(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
p := coreapi.ParseCid(edir.Cid()) p := coreiface.IpfsPath(edir.Cid())
if p.String() != emptyDir.String() { emptyDir, err := api.Object().New(ctx, options.Object.Type("unixfs-dir"))
t.Fatalf("expected path %s, got: %s", emptyDir, p) 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 { if err != coreiface.ErrIsDir {
t.Fatalf("expected ErrIsDir, got: %s", err) t.Fatalf("expected ErrIsDir, got: %s", err)
} }
@ -231,7 +244,7 @@ func TestCatNonUnixfs(t *testing.T) {
t.Error(err) 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") { if !strings.Contains(err.Error(), "proto: required field") {
t.Fatalf("expected protobuf error, got: %s", err) t.Fatalf("expected protobuf error, got: %s", err)
} }
@ -244,7 +257,11 @@ func TestCatOffline(t *testing.T) {
t.Error(err) 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 { if err != coreiface.ErrOffline {
t.Fatalf("expected ErrOffline, got: %s", err) t.Fatalf("expected ErrOffline, got: %s", err)
} }
@ -266,7 +283,10 @@ func TestLs(t *testing.T) {
if len(parts) != 2 { if len(parts) != 2 {
t.Errorf("unexpected path: %s", k) 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) links, err := api.Unixfs().Ls(ctx, p)
if err != nil { if err != nil {
@ -299,7 +319,12 @@ func TestLsEmptyDir(t *testing.T) {
t.Error(err) 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 { if err != nil {
t.Error(err) t.Error(err)
} }
@ -327,7 +352,7 @@ func TestLsNonUnixfs(t *testing.T) {
t.Error(err) 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 { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -13,7 +13,6 @@ import (
"time" "time"
core "github.com/ipfs/go-ipfs/core" core "github.com/ipfs/go-ipfs/core"
coreapi "github.com/ipfs/go-ipfs/core/coreapi"
coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface"
"github.com/ipfs/go-ipfs/importer" "github.com/ipfs/go-ipfs/importer"
dag "github.com/ipfs/go-ipfs/merkledag" dag "github.com/ipfs/go-ipfs/merkledag"
@ -160,7 +159,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
ipnsHostname = true ipnsHostname = true
} }
parsedPath, err := coreapi.ParsePath(urlPath) parsedPath, err := coreiface.ParsePath(urlPath)
if err != nil { if err != nil {
webError(w, "invalid ipfs path", err, http.StatusBadRequest) webError(w, "invalid ipfs path", err, http.StatusBadRequest)
return return
@ -288,7 +287,7 @@ func (i *gatewayHandler) getOrHeadHandler(ctx context.Context, w http.ResponseWr
return 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 { if err != nil {
internalWebError(w, err) internalWebError(w, err)
return return

View File

@ -171,7 +171,7 @@ func TestGatewayGet(t *testing.T) {
{"working.example.com", "/", http.StatusOK, "fnord"}, {"working.example.com", "/", http.StatusOK, "fnord"},
{"double.example.com", "/", http.StatusOK, "fnord"}, {"double.example.com", "/", http.StatusOK, "fnord"},
{"triple.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", "/", 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"}, {"broken.example.com", "/ipfs/" + k, http.StatusNotFound, "ipfs resolve -r /ipns/broken.example.com/ipfs/" + k + ": " + namesys.ErrResolveFailed.Error() + "\n"},
} { } {

View File

@ -19,10 +19,8 @@ import (
var ErrNoNamesys = errors.New( var ErrNoNamesys = errors.New(
"core/resolve: no Namesys on IpfsNode - can't resolve ipns entry") "core/resolve: no Namesys on IpfsNode - can't resolve ipns entry")
// Resolve resolves the given path by parsing out protocol-specific // ResolveIPNS resolves /ipns paths
// entries (e.g. /ipns/<node-key>) and then going through the /ipfs/ func ResolveIPNS(ctx context.Context, nsys namesys.NameSystem, p path.Path) (path.Path, error) {
// entries and returning the final node.
func Resolve(ctx context.Context, nsys namesys.NameSystem, r *resolver.Resolver, p path.Path) (ipld.Node, error) {
if strings.HasPrefix(p.String(), "/ipns/") { if strings.HasPrefix(p.String(), "/ipns/") {
evt := log.EventBegin(ctx, "resolveIpnsPath") evt := log.EventBegin(ctx, "resolveIpnsPath")
defer evt.Done() 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 // TODO(cryptix): we should be able to query the local cache for the path
if nsys == nil { if nsys == nil {
evt.Append(logging.LoggableMap{"error": ErrNoNamesys.Error()}) evt.Append(logging.LoggableMap{"error": ErrNoNamesys.Error()})
return nil, ErrNoNamesys return "", ErrNoNamesys
} }
seg := p.Segments() seg := p.Segments()
if len(seg) < 2 || seg[1] == "" { // just "/<protocol/>" without further segments if len(seg) < 2 || seg[1] == "" { // just "/<protocol/>" without further segments
evt.Append(logging.LoggableMap{"error": path.ErrNoComponents.Error()}) evt.Append(logging.LoggableMap{"error": path.ErrNoComponents.Error()})
return nil, path.ErrNoComponents return "", path.ErrNoComponents
} }
extensions := seg[2:] extensions := seg[2:]
resolvable, err := path.FromSegments("/", seg[0], seg[1]) resolvable, err := path.FromSegments("/", seg[0], seg[1])
if err != nil { if err != nil {
evt.Append(logging.LoggableMap{"error": err.Error()}) evt.Append(logging.LoggableMap{"error": err.Error()})
return nil, err return "", err
} }
respath, err := nsys.Resolve(ctx, resolvable.String()) respath, err := nsys.Resolve(ctx, resolvable.String())
if err != nil { if err != nil {
evt.Append(logging.LoggableMap{"error": err.Error()}) evt.Append(logging.LoggableMap{"error": err.Error()})
return nil, err return "", err
} }
segments := append(respath.Segments(), extensions...) segments := append(respath.Segments(), extensions...)
p, err = path.FromSegments("/", segments...) p, err = path.FromSegments("/", segments...)
if err != nil { if err != nil {
evt.Append(logging.LoggableMap{"error": err.Error()}) 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/<node-key>) 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) // ok, we have an IPFS path now (or what we'll treat as one)
return r.ResolvePath(ctx, p) return r.ResolvePath(ctx, p)

View File

@ -59,10 +59,11 @@ func (p Path) String() string {
return string(p) return string(p)
} }
// IsJustAKey returns true if the path is of the form <key> or /ipfs/<key>. // IsJustAKey returns true if the path is of the form <key> or /ipfs/<key>, or
// /ipld/<key>
func (p Path) IsJustAKey() bool { func (p Path) IsJustAKey() bool {
parts := p.Segments() 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 // 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 { if _, err := ParseCidToPath(parts[2]); err != nil {
return "", err return "", err
} }
} else if parts[1] != "ipns" { } else if parts[1] != "ipns" && parts[1] != "ipld" { //TODO: make this smarter
return "", ErrBadPath return "", ErrBadPath
} }
@ -161,7 +162,7 @@ func SplitList(pth string) []string {
// must be a Multihash) and return it separately. // must be a Multihash) and return it separately.
func SplitAbsPath(fpath Path) (*cid.Cid, []string, error) { func SplitAbsPath(fpath Path) (*cid.Cid, []string, error) {
parts := fpath.Segments() parts := fpath.Segments()
if parts[0] == "ipfs" { if parts[0] == "ipfs" || parts[0] == "ipld" {
parts = parts[1:] parts = parts[1:]
} }

View File

@ -9,6 +9,9 @@ func TestPathParsing(t *testing.T) {
"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true,
"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": true, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": true,
"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b/c/d/e/f": 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/a/b/c/d/e/f": true,
"/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true, "/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true,
"QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b/c/d/e/f": 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": false,
"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": false, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": false,
"/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": false, "/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": false,
"/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": false,
"/ipld/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n": true,
} }
for p, expected := range cases { for p, expected := range cases {
@ -57,6 +62,7 @@ func TestPopLastSegment(t *testing.T) {
"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": []string{"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n", "a"}, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a": []string{"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n", "a"},
"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": []string{"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a", "b"}, "/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a/b": []string{"/ipfs/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/a", "b"},
"/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/x/y/z": []string{"/ipns/QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n/x/y", "z"}, "/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 { for p, expected := range cases {

View File

@ -34,6 +34,9 @@ func (e ErrNoLink) Error() string {
return fmt.Sprintf("no link named %q under %s", e.Name, e.Node.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 // Resolver provides path resolution to IPFS
// It has a pointer to a DAGService, which is uses to resolve nodes. // 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 // 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 { type Resolver struct {
DAG ipld.NodeGetter 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. // NewBasicResolver constructs a new basic resolver.