dirbuilder.go 3.73 KB
Newer Older
1 2 3
package io

import (
4
	"context"
5 6
	"fmt"
	"os"
Jeromy's avatar
Jeromy committed
7

8 9
	mdag "github.com/ipfs/go-ipfs/merkledag"
	format "github.com/ipfs/go-ipfs/unixfs"
10 11
	hamt "github.com/ipfs/go-ipfs/unixfs/hamt"

12
	node "gx/ipfs/Qmb3Hm9QDFmfYuET4pu7Kyg8JV78jFa1nvZx5vnCZsK4ck/go-ipld-format"
13 14
)

15 16 17 18 19
// ShardSplitThreshold specifies how large of an unsharded directory
// the Directory code will generate. Adding entries over this value will
// result in the node being restructured into a sharded object.
var ShardSplitThreshold = 1000

Jeromy's avatar
Jeromy committed
20 21 22 23
// UseHAMTSharding is a global flag that signifies whether or not to use the
// HAMT sharding scheme for directory creation
var UseHAMTSharding = false

24 25 26 27
// DefaultShardWidth is the default value used for hamt sharding width.
var DefaultShardWidth = 256

type Directory struct {
28
	dserv   mdag.DAGService
29
	dirnode *mdag.ProtoNode
30

31
	shard *hamt.HamtShard
Henry's avatar
Henry committed
32 33
}

34 35 36
// NewDirectory returns a Directory. It needs a DAGService to add the Children
func NewDirectory(dserv mdag.DAGService) *Directory {
	db := new(Directory)
37
	db.dserv = dserv
Jeromy's avatar
Jeromy committed
38 39 40 41 42 43 44 45 46
	if UseHAMTSharding {
		s, err := hamt.NewHamtShard(dserv, DefaultShardWidth)
		if err != nil {
			panic(err) // will only panic if DefaultShardWidth is a bad value
		}
		db.shard = s
	} else {
		db.dirnode = format.EmptyDirNode()
	}
47 48 49
	return db
}

50 51 52 53 54 55 56
func NewDirectoryFromNode(dserv mdag.DAGService, nd node.Node) (*Directory, error) {
	pbnd, ok := nd.(*mdag.ProtoNode)
	if !ok {
		return nil, mdag.ErrNotProtobuf
	}

	pbd, err := format.FromBytes(pbnd.Data())
57
	if err != nil {
58
		return nil, err
59 60
	}

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
	switch pbd.GetType() {
	case format.TDirectory:
		return &Directory{
			dserv:   dserv,
			dirnode: pbnd.Copy().(*mdag.ProtoNode),
		}, nil
	case format.THAMTShard:
		shard, err := hamt.NewHamtFromDag(dserv, nd)
		if err != nil {
			return nil, err
		}

		return &Directory{
			dserv: dserv,
			shard: shard,
		}, nil
	default:
		return nil, fmt.Errorf("merkledag node was not a directory or shard")
79
	}
80
}
81

82 83 84
// AddChild adds a (name, key)-pair to the root node.
func (d *Directory) AddChild(ctx context.Context, name string, nd node.Node) error {
	if d.shard == nil {
Jeromy's avatar
Jeromy committed
85
		if !UseHAMTSharding {
86 87 88 89 90 91 92 93 94 95 96
			_ = d.dirnode.RemoveNodeLink(name)
			return d.dirnode.AddNodeLinkClean(name, nd)
		}

		err := d.switchToSharding(ctx)
		if err != nil {
			return err
		}
	}

	return d.shard.Set(ctx, name, nd)
97 98
}

99
func (d *Directory) switchToSharding(ctx context.Context) error {
100 101 102 103 104 105
	s, err := hamt.NewHamtShard(d.dserv, DefaultShardWidth)
	if err != nil {
		return err
	}

	d.shard = s
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
	for _, lnk := range d.dirnode.Links() {
		cnd, err := d.dserv.Get(ctx, lnk.Cid)
		if err != nil {
			return err
		}

		err = d.shard.Set(ctx, lnk.Name, cnd)
		if err != nil {
			return err
		}
	}

	d.dirnode = nil
	return nil
}

Jeromy's avatar
Jeromy committed
122
func (d *Directory) ForEachLink(ctx context.Context, f func(*node.Link) error) error {
123 124 125 126 127 128 129 130 131
	if d.shard == nil {
		for _, l := range d.dirnode.Links() {
			if err := f(l); err != nil {
				return err
			}
		}
		return nil
	}

Jeromy's avatar
Jeromy committed
132
	return d.shard.ForEachLink(ctx, f)
133 134
}

Jeromy's avatar
Jeromy committed
135
func (d *Directory) Links(ctx context.Context) ([]*node.Link, error) {
136 137 138 139
	if d.shard == nil {
		return d.dirnode.Links(), nil
	}

Jeromy's avatar
Jeromy committed
140
	return d.shard.EnumLinks(ctx)
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
}

func (d *Directory) Find(ctx context.Context, name string) (node.Node, error) {
	if d.shard == nil {
		lnk, err := d.dirnode.GetNodeLink(name)
		switch err {
		case mdag.ErrLinkNotFound:
			return nil, os.ErrNotExist
		default:
			return nil, err
		case nil:
		}

		return d.dserv.Get(ctx, lnk.Cid)
	}

	return d.shard.Find(ctx, name)
}

func (d *Directory) RemoveChild(ctx context.Context, name string) error {
	if d.shard == nil {
		return d.dirnode.RemoveNodeLink(name)
	}

	return d.shard.Remove(ctx, name)
}

// GetNode returns the root of this Directory
func (d *Directory) GetNode() (node.Node, error) {
	if d.shard == nil {
		return d.dirnode, nil
	}

	return d.shard.Node()
175
}