Skip to content

Commit

Permalink
use old go-path
Browse files Browse the repository at this point in the history
  • Loading branch information
magik6k committed Mar 3, 2022
1 parent 1bb6518 commit 7dcfeee
Show file tree
Hide file tree
Showing 3 changed files with 408 additions and 9 deletions.
198 changes: 198 additions & 0 deletions lib/oldpath/oldresolver/resolver.go
@@ -0,0 +1,198 @@
package oldresolver

import (
"context"
"errors"
"fmt"
"time"

path "github.com/filecoin-project/lotus/lib/oldpath"

cid "github.com/ipfs/go-cid"
ipld "github.com/ipfs/go-ipld-format"
logging "github.com/ipfs/go-log/v2"
dag "github.com/ipfs/go-merkledag"
)

var log = logging.Logger("pathresolv")

// ErrNoComponents is used when Paths after a protocol
// do not contain at least one component
var ErrNoComponents = errors.New(
"path must contain at least one component")

// ErrNoLink is returned when a link is not found in a path
type ErrNoLink struct {
Name string
Node cid.Cid
}

// Error implements the Error interface for ErrNoLink with a useful
// human readable message.
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
// the resolvers in namesys
type Resolver struct {
DAG ipld.NodeGetter

ResolveOnce ResolveOnce
}

// NewBasicResolver constructs a new basic resolver.
func NewBasicResolver(ds ipld.DAGService) *Resolver {
return &Resolver{
DAG: ds,
ResolveOnce: ResolveSingle,
}
}

// ResolveToLastNode walks the given path and returns the cid of the last node
// referenced by the path
func (r *Resolver) ResolveToLastNode(ctx context.Context, fpath path.Path) (cid.Cid, []string, error) {
c, p, err := path.SplitAbsPath(fpath)
if err != nil {
return cid.Cid{}, nil, err
}

if len(p) == 0 {
return c, nil, nil
}

nd, err := r.DAG.Get(ctx, c)
if err != nil {
return cid.Cid{}, nil, err
}

for len(p) > 0 {
lnk, rest, err := r.ResolveOnce(ctx, r.DAG, nd, p)

// Note: have to drop the error here as `ResolveOnce` doesn't handle 'leaf'
// paths (so e.g. for `echo '{"foo":123}' | ipfs dag put` we wouldn't be
// able to resolve `zdpu[...]/foo`)
if lnk == nil {
break
}

if err != nil {
if err == dag.ErrLinkNotFound {
err = ErrNoLink{Name: p[0], Node: nd.Cid()}
}
return cid.Cid{}, nil, err
}

next, err := lnk.GetNode(ctx, r.DAG)
if err != nil {
return cid.Cid{}, nil, err
}
nd = next
p = rest
}

if len(p) == 0 {
return nd.Cid(), nil, nil
}

// Confirm the path exists within the object
val, rest, err := nd.Resolve(p)
if err != nil {
if err == dag.ErrLinkNotFound {
err = ErrNoLink{Name: p[0], Node: nd.Cid()}
}
return cid.Cid{}, nil, err
}

if len(rest) > 0 {
return cid.Cid{}, nil, errors.New("path failed to resolve fully")
}
switch val.(type) {
case *ipld.Link:
return cid.Cid{}, nil, errors.New("inconsistent ResolveOnce / nd.Resolve")
default:
return nd.Cid(), p, nil
}
}

// ResolvePath fetches the node for given path. It returns the last item
// returned by ResolvePathComponents.
func (r *Resolver) ResolvePath(ctx context.Context, fpath path.Path) (ipld.Node, error) {
// validate path
if err := fpath.IsValid(); err != nil {
return nil, err
}

nodes, err := r.ResolvePathComponents(ctx, fpath)
if err != nil || nodes == nil {
return nil, err
}
return nodes[len(nodes)-1], err
}

// ResolveSingle simply resolves one hop of a path through a graph with no
// extra context (does not opaquely resolve through sharded nodes)
func ResolveSingle(ctx context.Context, ds ipld.NodeGetter, nd ipld.Node, names []string) (*ipld.Link, []string, error) {
return nd.ResolveLink(names)
}

// ResolvePathComponents fetches the nodes for each segment of the given path.
// It uses the first path component as a hash (key) of the first node, then
// resolves all other components walking the links, with ResolveLinks.
func (r *Resolver) ResolvePathComponents(ctx context.Context, fpath path.Path) ([]ipld.Node, error) {

h, parts, err := path.SplitAbsPath(fpath)
if err != nil {
return nil, err
}

log.Debug("resolve dag get")
nd, err := r.DAG.Get(ctx, h)
if err != nil {
return nil, err
}

return r.ResolveLinks(ctx, nd, parts)
}

// ResolveLinks iteratively resolves names by walking the link hierarchy.
// Every node is fetched from the DAGService, resolving the next name.
// Returns the list of nodes forming the path, starting with ndd. This list is
// guaranteed never to be empty.
//
// ResolveLinks(nd, []string{"foo", "bar", "baz"})
// would retrieve "baz" in ("bar" in ("foo" in nd.Links).Links).Links
func (r *Resolver) ResolveLinks(ctx context.Context, ndd ipld.Node, names []string) ([]ipld.Node, error) {
result := make([]ipld.Node, 0, len(names)+1)
result = append(result, ndd)
nd := ndd // dup arg workaround

// for each of the path components
for len(names) > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Minute)
defer cancel()

lnk, rest, err := r.ResolveOnce(ctx, r.DAG, nd, names)
if err == dag.ErrLinkNotFound {
return result, ErrNoLink{Name: names[0], Node: nd.Cid()}
} else if err != nil {
return result, err
}

nextnode, err := lnk.GetNode(ctx, r.DAG)
if err != nil {
return result, err
}

nd = nextnode
result = append(result, nextnode)
names = rest
}
return result, nil
}
199 changes: 199 additions & 0 deletions lib/oldpath/path.go
@@ -0,0 +1,199 @@
package oldpath

import (
"fmt"
"path"
"strings"

cid "github.com/ipfs/go-cid"
)

// helper type so path parsing errors include the path
type pathError struct {
error error
path string
}

func (e *pathError) Error() string {
return fmt.Sprintf("invalid path %q: %s", e.path, e.error)
}

func (e *pathError) Unwrap() error {
return e.error
}

func (e *pathError) Path() string {
return e.path
}

// A Path represents an ipfs content path:
// * /<cid>/path/to/file
// * /ipfs/<cid>
// * /ipns/<cid>/path/to/folder
// * etc
type Path string

// ^^^
// TODO: debate making this a private struct wrapped in a public interface
// would allow us to control creation, and cache segments.

// FromString safely converts a string type to a Path type.
func FromString(s string) Path {
return Path(s)
}

// FromCid safely converts a cid.Cid type to a Path type.
func FromCid(c cid.Cid) Path {
return Path("/ipfs/" + c.String())
}

// Segments returns the different elements of a path
// (elements are delimited by a /).
func (p Path) Segments() []string {
cleaned := path.Clean(string(p))
segments := strings.Split(cleaned, "/")

// Ignore leading slash
if len(segments[0]) == 0 {
segments = segments[1:]
}

return segments
}

// String converts a path to string.
func (p Path) String() string {
return string(p)
}

// IsJustAKey returns true if the path is of the form <key> or /ipfs/<key>, or
// /ipld/<key>
func (p Path) IsJustAKey() bool {
parts := p.Segments()
return len(parts) == 2 && (parts[0] == "ipfs" || parts[0] == "ipld")
}

// PopLastSegment returns a new Path without its final segment, and the final
// segment, separately. If there is no more to pop (the path is just a key),
// the original path is returned.
func (p Path) PopLastSegment() (Path, string, error) {

if p.IsJustAKey() {
return p, "", nil
}

segs := p.Segments()
newPath, err := ParsePath("/" + strings.Join(segs[:len(segs)-1], "/"))
if err != nil {
return "", "", err
}

return newPath, segs[len(segs)-1], nil
}

// FromSegments returns a path given its different segments.
func FromSegments(prefix string, seg ...string) (Path, error) {
return ParsePath(prefix + strings.Join(seg, "/"))
}

// ParsePath returns a well-formed ipfs Path.
// The returned path will always be prefixed with /ipfs/ or /ipns/.
// The prefix will be added if not present in the given string.
// This function will return an error when the given string is
// not a valid ipfs path.
func ParsePath(txt string) (Path, error) {
parts := strings.Split(txt, "/")
if len(parts) == 1 {
kp, err := ParseCidToPath(txt)
if err == nil {
return kp, nil
}
}

// if the path doesnt begin with a '/'
// we expect this to start with a hash, and be an 'ipfs' path
if parts[0] != "" {
if _, err := cid.Decode(parts[0]); err != nil {
return "", &pathError{error: err, path: txt}
}
// The case when the path starts with hash without a protocol prefix
return Path("/ipfs/" + txt), nil
}

if len(parts) < 3 {
return "", &pathError{error: fmt.Errorf("path does not begin with '/'"), path: txt}
}

//TODO: make this smarter
switch parts[1] {
case "ipfs", "ipld":
if parts[2] == "" {
return "", &pathError{error: fmt.Errorf("not enough path components"), path: txt}
}
// Validate Cid.
_, err := cid.Decode(parts[2])
if err != nil {
return "", &pathError{error: fmt.Errorf("invalid CID: %s", err), path: txt}
}
case "ipns":
if parts[2] == "" {
return "", &pathError{error: fmt.Errorf("not enough path components"), path: txt}
}
default:
return "", &pathError{error: fmt.Errorf("unknown namespace %q", parts[1]), path: txt}
}

return Path(txt), nil
}

// ParseCidToPath takes a CID in string form and returns a valid ipfs Path.
func ParseCidToPath(txt string) (Path, error) {
if txt == "" {
return "", &pathError{error: fmt.Errorf("empty"), path: txt}
}

c, err := cid.Decode(txt)
if err != nil {
return "", &pathError{error: err, path: txt}
}

return FromCid(c), nil
}

// IsValid checks if a path is a valid ipfs Path.
func (p *Path) IsValid() error {
_, err := ParsePath(p.String())
return err
}

// Join joins strings slices using /
func Join(pths []string) string {
return strings.Join(pths, "/")
}

// SplitList splits strings usings /
func SplitList(pth string) []string {
return strings.Split(pth, "/")
}

// SplitAbsPath clean up and split fpath. It extracts the first component (which
// must be a Multihash) and return it separately.
func SplitAbsPath(fpath Path) (cid.Cid, []string, error) {
parts := fpath.Segments()
if parts[0] == "ipfs" || parts[0] == "ipld" {
parts = parts[1:]
}

// if nothing, bail.
if len(parts) == 0 {
return cid.Cid{}, nil, &pathError{error: fmt.Errorf("empty"), path: string(fpath)}
}

c, err := cid.Decode(parts[0])
// first element in the path is a cid
if err != nil {
return cid.Cid{}, nil, &pathError{error: fmt.Errorf("invalid CID: %s", err), path: string(fpath)}
}

return c, parts[1:], nil
}

0 comments on commit 7dcfeee

Please sign in to comment.