1
+package io
2
+
3
+import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+
8
+ mdag "github.com/ipfs/go-ipfs/merkledag"
9
+ format "github.com/ipfs/go-ipfs/unixfs"
10
+ hamt "github.com/ipfs/go-ipfs/unixfs/hamt"
11
+
12
+ ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format"
13
+ cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
14
+)
15
+
16
+// ShardSplitThreshold specifies how large of an unsharded directory
17
+// the Directory code will generate. Adding entries over this value will
18
+// result in the node being restructured into a sharded object.
19
+var ShardSplitThreshold = 1000
20
+
21
+// UseHAMTSharding is a global flag that signifies whether or not to use the
22
+// HAMT sharding scheme for directory creation
23
+var UseHAMTSharding = false
24
+
25
+// DefaultShardWidth is the default value used for hamt sharding width.
26
+var DefaultShardWidth = 256
27
+
28
+// Directory defines a UnixFS directory. It is used for creating, reading and
29
+// editing directories. It allows to work with different directory schemes,
30
+// like the basic or the HAMT implementation.
31
+//
32
+// It just allows to perform explicit edits on a single directory, working with
33
+// directory trees is out of its scope, they are managed by the MFS layer
34
+// (which is the main consumer of this interface).
35
+type Directory interface {
36
+
37
+ // SetPrefix sets the CID prefix of the root node.
38
+ SetPrefix(*cid.Prefix)
39
+
40
+ // AddChild adds a (name, key) pair to the root node.
41
+ AddChild(context.Context, string, ipld.Node) error
42
+
43
+ // ForEachLink applies the given function to Links in the directory.
44
+ ForEachLink(context.Context, func(*ipld.Link) error) error
45
+
46
+ // Links returns the all the links in the directory node.
47
+ Links(context.Context) ([]*ipld.Link, error)
48
+
49
+ // Find returns the root node of the file named 'name' within this directory.
50
+ // In the case of HAMT-directories, it will traverse the tree.
51
+ Find(context.Context, string) (ipld.Node, error)
52
+
53
+ // RemoveChild removes the child with the given name.
54
+ RemoveChild(context.Context, string) error
55
+
56
+ // GetNode returns the root of this directory.
57
+ GetNode() (ipld.Node, error)
58
+
59
+ // GetPrefix returns the CID Prefix used.
60
+ GetPrefix() *cid.Prefix
61
+}
62
+
63
+// TODO: Evaluate removing `dserv` from this layer and providing it in MFS.
64
+// (The functions should in that case add a `DAGService` argument.)
65
+
66
+// BasicDirectory is the basic implementation of `Directory`. All the entries
67
+// are stored in a single node.
68
+type BasicDirectory struct {
69
+ node *mdag.ProtoNode
70
+ dserv ipld.DAGService
71
+}
72
+
73
+// HAMTDirectory is the HAMT implementation of `Directory`.
74
+// (See package `hamt` for more information.)
75
+type HAMTDirectory struct {
76
+ shard *hamt.Shard
77
+ dserv ipld.DAGService
78
+}
79
+
80
+// NewDirectory returns a Directory. It needs a `DAGService` to add the children.
81
+func NewDirectory(dserv ipld.DAGService) Directory {
82
+ if UseHAMTSharding {
83
+ dir := new(HAMTDirectory)
84
+ s, err := hamt.NewShard(dserv, DefaultShardWidth)
85
+ if err != nil {
86
+ panic(err) // will only panic if DefaultShardWidth is a bad value
87
+ }
88
+ dir.shard = s
89
+ dir.dserv = dserv
90
+ return dir
91
+ }
92
+
93
+ dir := new(BasicDirectory)
94
+ dir.node = format.EmptyDirNode()
95
+ dir.dserv = dserv
96
+ return dir
97
+}
98
+
99
+// ErrNotADir implies that the given node was not a unixfs directory
100
+var ErrNotADir = fmt.Errorf("merkledag node was not a directory or shard")
101
+
102
+// NewDirectoryFromNode loads a unixfs directory from the given IPLD node and
103
+// DAGService.
104
+func NewDirectoryFromNode(dserv ipld.DAGService, node ipld.Node) (Directory, error) {
105
+ protoBufNode, ok := node.(*mdag.ProtoNode)
106
+ if !ok {
107
+ return nil, ErrNotADir
108
+ }
109
+
110
+ fsNode, err := format.FSNodeFromBytes(protoBufNode.Data())
111
+ if err != nil {
112
+ return nil, err
113
+ }
114
+
115
+ switch fsNode.GetType() {
116
+ case format.TDirectory:
117
+ return &BasicDirectory{
118
+ dserv: dserv,
119
+ node: protoBufNode.Copy().(*mdag.ProtoNode),
120
+ }, nil
121
+ case format.THAMTShard:
122
+ shard, err := hamt.NewHamtFromDag(dserv, node)
123
+ if err != nil {
124
+ return nil, err
125
+ }
126
+ return &HAMTDirectory{
127
+ dserv: dserv,
128
+ shard: shard,
129
+ }, nil
130
+ }
131
+
132
+ return nil, ErrNotADir
133
+}
134
+
135
+// SetPrefix implements the `Directory` interface.
136
+func (d *BasicDirectory) SetPrefix(prefix *cid.Prefix) {
137
+ d.node.SetPrefix(prefix)
138
+}
139
+
140
+// AddChild implements the `Directory` interface. It adds (or replaces)
141
+// a link to the given `node` under `name`.
142
+func (d *BasicDirectory) AddChild(ctx context.Context, name string, node ipld.Node) error {
143
+ d.node.RemoveNodeLink(name)
144
+ // Remove old link (if it existed), don't check a potential `ErrNotFound`.
145
+
146
+ return d.node.AddNodeLink(name, node)
147
+}
148
+
149
+// ForEachLink implements the `Directory` interface.
150
+func (d *BasicDirectory) ForEachLink(ctx context.Context, f func(*ipld.Link) error) error {
151
+ for _, l := range d.node.Links() {
152
+ if err := f(l); err != nil {
153
+ return err
154
+ }
155
+ }
156
+ return nil
157
+}
158
+
159
+// Links implements the `Directory` interface.
160
+func (d *BasicDirectory) Links(ctx context.Context) ([]*ipld.Link, error) {
161
+ return d.node.Links(), nil
162
+}
163
+
164
+// Find implements the `Directory` interface.
165
+func (d *BasicDirectory) Find(ctx context.Context, name string) (ipld.Node, error) {
166
+ lnk, err := d.node.GetNodeLink(name)
167
+ if err == mdag.ErrLinkNotFound {
168
+ err = os.ErrNotExist
169
+ }
170
+ if err != nil {
171
+ return nil, err
172
+ }
173
+
174
+ return d.dserv.Get(ctx, lnk.Cid)
175
+}
176
+
177
+// RemoveChild implements the `Directory` interface.
178
+func (d *BasicDirectory) RemoveChild(ctx context.Context, name string) error {
179
+ return d.node.RemoveNodeLink(name)
180
+}
181
+
182
+// GetNode implements the `Directory` interface.
183
+func (d *BasicDirectory) GetNode() (ipld.Node, error) {
184
+ return d.node, nil
185
+}
186
+
187
+// GetPrefix implements the `Directory` interface.
188
+func (d *BasicDirectory) GetPrefix() *cid.Prefix {
189
+ return &d.node.Prefix
190
+}
191
+
192
+// SwitchToSharding returns a HAMT implementation of this directory.
193
+func (d *BasicDirectory) SwitchToSharding(ctx context.Context) (Directory, error) {
194
+ hamtDir := new(HAMTDirectory)
195
+ hamtDir.dserv = d.dserv
196
+
197
+ shard, err := hamt.NewShard(d.dserv, DefaultShardWidth)
198
+ if err != nil {
199
+ return nil, err
200
+ }
201
+ shard.SetPrefix(&d.node.Prefix)
202
+ hamtDir.shard = shard
203
+
204
+ for _, lnk := range d.node.Links() {
205
+ node, err := d.dserv.Get(ctx, lnk.Cid)
206
+ if err != nil {
207
+ return nil, err
208
+ }
209
+
210
+ err = hamtDir.shard.Set(ctx, lnk.Name, node)
211
+ if err != nil {
212
+ return nil, err
213
+ }
214
+ }
215
+
216
+ return hamtDir, nil
217
+}
218
+
219
+// SetPrefix implements the `Directory` interface.
220
+func (d *HAMTDirectory) SetPrefix(prefix *cid.Prefix) {
221
+ d.shard.SetPrefix(prefix)
222
+}
223
+
224
+// AddChild implements the `Directory` interface.
225
+func (d *HAMTDirectory) AddChild(ctx context.Context, name string, nd ipld.Node) error {
226
+ return d.shard.Set(ctx, name, nd)
227
+}
228
+
229
+// ForEachLink implements the `Directory` interface.
230
+func (d *HAMTDirectory) ForEachLink(ctx context.Context, f func(*ipld.Link) error) error {
231
+ return d.shard.ForEachLink(ctx, f)
232
+}
233
+
234
+// Links implements the `Directory` interface.
235
+func (d *HAMTDirectory) Links(ctx context.Context) ([]*ipld.Link, error) {
236
+ return d.shard.EnumLinks(ctx)
237
+}
238
+
239
+// Find implements the `Directory` interface. It will traverse the tree.
240
+func (d *HAMTDirectory) Find(ctx context.Context, name string) (ipld.Node, error) {
241
+ lnk, err := d.shard.Find(ctx, name)
242
+ if err != nil {
243
+ return nil, err
244
+ }
245
+
246
+ return lnk.GetNode(ctx, d.dserv)
247
+}
248
+
249
+// RemoveChild implements the `Directory` interface.
250
+func (d *HAMTDirectory) RemoveChild(ctx context.Context, name string) error {
251
+ return d.shard.Remove(ctx, name)
252
+}
253
+
254
+// GetNode implements the `Directory` interface.
255
+func (d *HAMTDirectory) GetNode() (ipld.Node, error) {
256
+ return d.shard.Node()
257
+}
258
+
259
+// GetPrefix implements the `Directory` interface.
260
+func (d *HAMTDirectory) GetPrefix() *cid.Prefix {
261
+ return d.shard.Prefix()
262
+}