diffenum.go 2.36 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3 4 5 6 7 8
package dagutils

import (
	"context"
	"fmt"

	mdag "github.com/ipfs/go-ipfs/merkledag"

Steven Allen's avatar
Steven Allen committed
9
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
10
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
Jeromy's avatar
Jeromy committed
11 12 13 14 15
)

// DiffEnumerate fetches every object in the graph pointed to by 'to' that is
// not in 'from'. This can be used to more efficiently fetch a graph if you can
// guarantee you already have the entirety of 'from'
16
func DiffEnumerate(ctx context.Context, dserv ipld.NodeGetter, from, to *cid.Cid) error {
Jeromy's avatar
Jeromy committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30
	fnd, err := dserv.Get(ctx, from)
	if err != nil {
		return fmt.Errorf("get %s: %s", from, err)
	}

	tnd, err := dserv.Get(ctx, to)
	if err != nil {
		return fmt.Errorf("get %s: %s", to, err)
	}

	diff := getLinkDiff(fnd, tnd)

	sset := cid.NewSet()
	for _, c := range diff {
31 32 33 34 35 36 37 38 39 40 41 42 43
		// Since we're already assuming we have everything in the 'from' graph,
		// add all those cids to our 'already seen' set to avoid potentially
		// enumerating them later
		if c.bef != nil {
			sset.Add(c.bef)
		}
	}
	for _, c := range diff {
		if c.bef == nil {
			if sset.Has(c.aft) {
				continue
			}
			err := mdag.EnumerateChildrenAsync(ctx, mdag.GetLinksDirect(dserv), c.aft, sset.Visit)
Jeromy's avatar
Jeromy committed
44 45 46 47
			if err != nil {
				return err
			}
		} else {
48
			err := DiffEnumerate(ctx, dserv, c.bef, c.aft)
Jeromy's avatar
Jeromy committed
49 50 51 52 53 54 55 56 57
			if err != nil {
				return err
			}
		}
	}

	return nil
}

Jeromy's avatar
Jeromy committed
58 59 60
// if both bef and aft are not nil, then that signifies bef was replaces with aft.
// if bef is nil and aft is not, that means aft was newly added
// if aft is nil and bef is not, that means bef was deleted
Jeromy's avatar
Jeromy committed
61
type diffpair struct {
62
	bef, aft *cid.Cid
Jeromy's avatar
Jeromy committed
63 64
}

Jeromy's avatar
Jeromy committed
65 66
// getLinkDiff returns a changeset between nodes 'a' and 'b'. Currently does
// not log deletions as our usecase doesnt call for this.
67 68 69
func getLinkDiff(a, b ipld.Node) []diffpair {
	ina := make(map[string]*ipld.Link)
	inb := make(map[string]*ipld.Link)
Ian Preston's avatar
Ian Preston committed
70 71 72 73
	var aonly []*cid.Cid
	for _, l := range b.Links() {
		inb[l.Cid.KeyString()] = l
	}
Jeromy's avatar
Jeromy committed
74
	for _, l := range a.Links() {
75
		var key = l.Cid.KeyString()
Ian Preston's avatar
Ian Preston committed
76 77
		ina[key] = l
		if inb[key] == nil {
Ian Preston's avatar
Ian Preston committed
78 79
			aonly = append(aonly, l.Cid)
		}
Jeromy's avatar
Jeromy committed
80 81 82
	}

	var out []diffpair
Ian Preston's avatar
Ian Preston committed
83
	var aindex int
Jeromy's avatar
Jeromy committed
84 85

	for _, l := range b.Links() {
Ian Preston's avatar
Ian Preston committed
86
		if ina[l.Cid.KeyString()] != nil {
Jeromy's avatar
Jeromy committed
87 88 89
			continue
		}

Ian Preston's avatar
Ian Preston committed
90 91 92 93
		if aindex < len(aonly) {
			out = append(out, diffpair{bef: aonly[aindex], aft: l.Cid})
			aindex++
		} else {
94
			out = append(out, diffpair{aft: l.Cid})
Jeromy's avatar
Jeromy committed
95 96 97 98 99
			continue
		}
	}
	return out
}