dirbuilder.go 4.92 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 13
	ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format"
	cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
14 15
)

16 17 18 19 20
// 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
21 22 23 24
// UseHAMTSharding is a global flag that signifies whether or not to use the
// HAMT sharding scheme for directory creation
var UseHAMTSharding = false

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

Hector Sanjuan's avatar
Hector Sanjuan committed
28 29 30
// Directory allows to work with UnixFS directory nodes, adding and removing
// children. It allows to work with different directory schemes,
// like the classic or the HAMT one.
31
type Directory struct {
32
	dserv   ipld.DAGService
33
	dirnode *mdag.ProtoNode
34

Hector Sanjuan's avatar
Hector Sanjuan committed
35
	shard *hamt.Shard
Henry's avatar
Henry committed
36 37
}

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

Jeromy's avatar
Jeromy committed
54
// ErrNotADir implies that the given node was not a unixfs directory
55 56
var ErrNotADir = fmt.Errorf("merkledag node was not a directory or shard")

Steven Allen's avatar
Steven Allen committed
57 58
// NewDirectoryFromNode loads a unixfs directory from the given IPLD node and
// DAGService.
59
func NewDirectoryFromNode(dserv ipld.DAGService, nd ipld.Node) (*Directory, error) {
60 61
	pbnd, ok := nd.(*mdag.ProtoNode)
	if !ok {
62
		return nil, ErrNotADir
63 64 65
	}

	pbd, err := format.FromBytes(pbnd.Data())
66
	if err != nil {
67
		return nil, err
68 69
	}

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
	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:
87
		return nil, ErrNotADir
88
	}
89
}
90

91
// SetPrefix sets the prefix of the root node
92
func (d *Directory) SetPrefix(prefix *cid.Prefix) {
93 94 95
	if d.dirnode != nil {
		d.dirnode.SetPrefix(prefix)
	}
96 97 98
	if d.shard != nil {
		d.shard.SetPrefix(prefix)
	}
99 100
}

101
// AddChild adds a (name, key)-pair to the root node.
102
func (d *Directory) AddChild(ctx context.Context, name string, nd ipld.Node) error {
103
	if d.shard == nil {
Jeromy's avatar
Jeromy committed
104
		if !UseHAMTSharding {
105
			_ = d.dirnode.RemoveNodeLink(name)
106
			return d.dirnode.AddNodeLink(name, nd)
107 108 109 110 111 112 113 114 115
		}

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

	return d.shard.Set(ctx, name, nd)
116 117
}

118
func (d *Directory) switchToSharding(ctx context.Context) error {
Hector Sanjuan's avatar
Hector Sanjuan committed
119
	s, err := hamt.NewShard(d.dserv, DefaultShardWidth)
120 121 122
	if err != nil {
		return err
	}
123
	s.SetPrefix(&d.dirnode.Prefix)
124 125

	d.shard = s
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
	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
}

Hector Sanjuan's avatar
Hector Sanjuan committed
142
// ForEachLink applies the given function to Links in the directory.
143
func (d *Directory) ForEachLink(ctx context.Context, f func(*ipld.Link) error) error {
144 145 146 147 148 149 150 151 152
	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
153
	return d.shard.ForEachLink(ctx, f)
154 155
}

Hector Sanjuan's avatar
Hector Sanjuan committed
156
// Links returns the all the links in the directory node.
157
func (d *Directory) Links(ctx context.Context) ([]*ipld.Link, error) {
158 159 160 161
	if d.shard == nil {
		return d.dirnode.Links(), nil
	}

Jeromy's avatar
Jeromy committed
162
	return d.shard.EnumLinks(ctx)
163 164
}

165 166
// Find returns the root node of the file named 'name' within this directory.
// In the case of HAMT-directories, it will traverse the tree.
167
func (d *Directory) Find(ctx context.Context, name string) (ipld.Node, error) {
168 169 170 171 172 173 174 175 176 177 178 179 180
	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)
	}

Jeromy's avatar
Jeromy committed
181 182 183 184 185 186
	lnk, err := d.shard.Find(ctx, name)
	if err != nil {
		return nil, err
	}

	return lnk.GetNode(ctx, d.dserv)
187 188
}

Hector Sanjuan's avatar
Hector Sanjuan committed
189
// RemoveChild removes the child with the given name.
190 191 192 193 194 195 196 197 198
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
199
func (d *Directory) GetNode() (ipld.Node, error) {
200 201 202 203 204
	if d.shard == nil {
		return d.dirnode, nil
	}

	return d.shard.Node()
205
}
206

Kevin Atkinson's avatar
Kevin Atkinson committed
207
// GetPrefix returns the CID Prefix used
208 209 210 211 212 213 214
func (d *Directory) GetPrefix() *cid.Prefix {
	if d.shard == nil {
		return &d.dirnode.Prefix
	}

	return d.shard.Prefix()
}