base.go 2.81 KB
Newer Older
1 2 3 4
package namesys

import (
	"strings"
Steven Allen's avatar
Steven Allen committed
5
	"time"
6

7
	context "context"
8

Dirk McCormick's avatar
Dirk McCormick committed
9
	opts "github.com/ipfs/go-ipfs/namesys/opts"
Steven Allen's avatar
Steven Allen committed
10
	path "gx/ipfs/QmcjwUb36Z16NJkvDX6ccXPqsFswo6AsRXynyXcLLCphV2/go-path"
11 12
)

13 14 15 16 17 18
type onceResult struct {
	value path.Path
	ttl   time.Duration
	err   error
}

19 20
type resolver interface {
	// resolveOnce looks up a name once (without recursion).
21 22 23
	resolveOnce(ctx context.Context, name string, options opts.ResolveOpts) (value path.Path, ttl time.Duration, err error)

	resolveOnceAsync(ctx context.Context, name string, options opts.ResolveOpts) <-chan onceResult
24 25 26
}

// resolve is a helper for implementing Resolver.ResolveN using resolveOnce.
27
func resolve(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) (path.Path, error) {
Dirk McCormick's avatar
Dirk McCormick committed
28
	depth := options.Depth
29
	for {
Steven Allen's avatar
Steven Allen committed
30
		p, _, err := r.resolveOnce(ctx, name, options)
31 32 33
		if err != nil {
			return "", err
		}
34
		log.Debugf("resolved %s to %s", name, p.String())
35 36 37 38 39 40 41 42 43 44

		if strings.HasPrefix(p.String(), "/ipfs/") {
			// we've bottomed out with an IPFS path
			return p, nil
		}

		if depth == 1 {
			return p, ErrResolveRecursion
		}

45
		if !strings.HasPrefix(p.String(), prefix) {
46 47
			return p, nil
		}
48
		name = strings.TrimPrefix(p.String(), prefix)
49 50 51 52 53 54

		if depth > 1 {
			depth--
		}
	}
}
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

//TODO:
// - better error handling
func resolveAsyncDo(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) <-chan Result {
	resCh := r.resolveOnceAsync(ctx, name, options)
	depth := options.Depth
	outCh := make(chan Result)

	go func() {
		defer close(outCh)
		var subCh <-chan Result
		var cancelSub context.CancelFunc

		for {
			select {
			case res, ok := <-resCh:
				if !ok {
					resCh = nil
					continue
				}
Łukasz Magiera's avatar
Łukasz Magiera committed
75 76 77 78 79

				if res.err != nil {
					outCh <- Result{Err: res.err}
					return
				}
80 81
				log.Debugf("resolved %s to %s", name, res.value.String())
				if strings.HasPrefix(res.value.String(), "/ipfs/") {
Łukasz Magiera's avatar
Łukasz Magiera committed
82
					outCh <- Result{Err: res.err}
83 84 85 86 87
					continue
				}
				p := strings.TrimPrefix(res.value.String(), prefix)

				if depth == 1 {
Łukasz Magiera's avatar
Łukasz Magiera committed
88
					outCh <- Result{Err: ErrResolveRecursion}
89 90 91 92 93 94 95 96 97 98 99 100 101 102
					continue
				}

				subopts := options
				if subopts.Depth > 1 {
					subopts.Depth--
				}

				var subCtx context.Context
				if subCh != nil {
					// Cancel previous recursive resolve since it won't be used anyways
					cancelSub()
				}
				subCtx, cancelSub = context.WithCancel(ctx)
Łukasz Magiera's avatar
Łukasz Magiera committed
103
				defer cancelSub()
104 105 106 107 108 109 110

				subCh = resolveAsyncDo(subCtx, r, p, subopts, prefix)
			case res, ok := <-subCh:
				if !ok {
					subCh = nil
					continue
				}
Łukasz Magiera's avatar
Łukasz Magiera committed
111 112 113 114 115 116

				if res.Err != nil {
					outCh <- Result{Err: res.Err}
					return
				}

117 118 119 120 121 122 123 124 125 126 127
				outCh <- res
			case <-ctx.Done():
			}
		}
	}()
	return outCh
}

func resolveAsync(ctx context.Context, r resolver, name string, options opts.ResolveOpts, prefix string) <-chan Result {
	return resolveAsyncDo(ctx, r, name, options, prefix)
}