dir.go 9.3 KB
Newer Older
1
package mfs
2 3

import (
4
	"context"
5 6 7
	"errors"
	"fmt"
	"os"
Jeromy's avatar
Jeromy committed
8
	"path"
Jeromy's avatar
Jeromy committed
9
	"sort"
10
	"sync"
Jeromy's avatar
Jeromy committed
11
	"time"
Jeromy's avatar
Jeromy committed
12

13 14
	dag "github.com/ipfs/go-ipfs/merkledag"
	ft "github.com/ipfs/go-ipfs/unixfs"
15
	uio "github.com/ipfs/go-ipfs/unixfs/io"
16
	ufspb "github.com/ipfs/go-ipfs/unixfs/pb"
17

18 19
	ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format"
	cid "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid"
20 21
)

Jeromy's avatar
Jeromy committed
22 23
var ErrNotYetImplemented = errors.New("not yet implemented")
var ErrInvalidChild = errors.New("invalid child node")
24
var ErrDirExists = errors.New("directory already has entry by that name")
Jeromy's avatar
Jeromy committed
25

26
type Directory struct {
27
	dserv  ipld.DAGService
28 29
	parent childCloser

30
	childDirs map[string]*Directory
31
	files     map[string]*File
32 33

	lock sync.Mutex
34
	ctx  context.Context
35

36 37 38
	// UnixFS directory implementation used for creating,
	// reading and editing directories.
	unixfsDir uio.Directory
39

Jeromy's avatar
Jeromy committed
40 41
	modTime time.Time

42
	name string
43 44
}

Steven Allen's avatar
Steven Allen committed
45 46 47 48
// NewDirectory constructs a new MFS directory.
//
// You probably don't want to call this directly. Instead, construct a new root
// using NewRoot.
49
func NewDirectory(ctx context.Context, name string, node ipld.Node, parent childCloser, dserv ipld.DAGService) (*Directory, error) {
50 51 52
	db, err := uio.NewDirectoryFromNode(dserv, node)
	if err != nil {
		return nil, err
53
	}
54 55

	return &Directory{
56 57 58 59 60 61 62 63
		dserv:     dserv,
		ctx:       ctx,
		name:      name,
		unixfsDir: db,
		parent:    parent,
		childDirs: make(map[string]*Directory),
		files:     make(map[string]*File),
		modTime:   time.Now(),
64
	}, nil
65 66
}

67 68
// GetPrefix gets the CID prefix of the root node
func (d *Directory) GetPrefix() *cid.Prefix {
69
	return d.unixfsDir.GetPrefix()
70 71
}

Kevin Atkinson's avatar
Kevin Atkinson committed
72
// SetPrefix sets the CID prefix
73
func (d *Directory) SetPrefix(prefix *cid.Prefix) {
74
	d.unixfsDir.SetPrefix(prefix)
75 76
}

Jeromy's avatar
Jeromy committed
77
// closeChild updates the child by the given name to the dag node 'nd'
Jeromy's avatar
Jeromy committed
78
// and changes its own dag node
79
func (d *Directory) closeChild(name string, nd ipld.Node, sync bool) error {
80
	mynd, err := d.closeChildUpdate(name, nd, sync)
81 82 83 84
	if err != nil {
		return err
	}

85 86 87 88
	if sync {
		return d.parent.closeChild(d.name, mynd, true)
	}
	return nil
89 90 91
}

// closeChildUpdate is the portion of closeChild that needs to be locked around
92
func (d *Directory) closeChildUpdate(name string, nd ipld.Node, sync bool) (*dag.ProtoNode, error) {
93
	d.lock.Lock()
Jeromy's avatar
Jeromy committed
94
	defer d.lock.Unlock()
95 96

	err := d.updateChild(name, nd)
97
	if err != nil {
98
		return nil, err
99 100
	}

101 102 103 104
	if sync {
		return d.flushCurrentNode()
	}
	return nil, nil
105 106
}

107
func (d *Directory) flushCurrentNode() (*dag.ProtoNode, error) {
108
	nd, err := d.unixfsDir.GetNode()
109 110 111 112
	if err != nil {
		return nil, err
	}

113
	err = d.dserv.Add(d.ctx, nd)
114 115 116
	if err != nil {
		return nil, err
	}
117

118 119 120
	pbnd, ok := nd.(*dag.ProtoNode)
	if !ok {
		return nil, dag.ErrNotProtobuf
121 122
	}

123 124 125
	return pbnd.Copy().(*dag.ProtoNode), nil
}

126
func (d *Directory) updateChild(name string, nd ipld.Node) error {
127
	err := d.AddUnixFSChild(name, nd)
128 129 130 131
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
132 133
	d.modTime = time.Now()

134
	return nil
135 136 137 138 139 140
}

func (d *Directory) Type() NodeType {
	return TDir
}

141 142 143
// childNode returns a FSNode under this directory by the given name if it exists.
// it does *not* check the cached dirs and files
func (d *Directory) childNode(name string) (FSNode, error) {
Jeromy's avatar
Jeromy committed
144 145 146 147 148
	nd, err := d.childFromDag(name)
	if err != nil {
		return nil, err
	}

149 150 151 152
	return d.cacheNode(name, nd)
}

// cacheNode caches a node into d.childDirs or d.files and returns the FSNode.
153
func (d *Directory) cacheNode(name string, nd ipld.Node) (FSNode, error) {
Jeromy's avatar
Jeromy committed
154 155 156 157 158 159
	switch nd := nd.(type) {
	case *dag.ProtoNode:
		i, err := ft.FromBytes(nd.Data())
		if err != nil {
			return nil, err
		}
Jeromy's avatar
Jeromy committed
160

Jeromy's avatar
Jeromy committed
161
		switch i.GetType() {
162 163 164 165 166 167
		case ufspb.Data_Directory, ufspb.Data_HAMTShard:
			ndir, err := NewDirectory(d.ctx, name, nd, d, d.dserv)
			if err != nil {
				return nil, err
			}

Jeromy's avatar
Jeromy committed
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
			d.childDirs[name] = ndir
			return ndir, nil
		case ufspb.Data_File, ufspb.Data_Raw, ufspb.Data_Symlink:
			nfi, err := NewFile(name, nd, d, d.dserv)
			if err != nil {
				return nil, err
			}
			d.files[name] = nfi
			return nfi, nil
		case ufspb.Data_Metadata:
			return nil, ErrNotYetImplemented
		default:
			return nil, ErrInvalidChild
		}
	case *dag.RawNode:
183 184 185 186 187 188
		nfi, err := NewFile(name, nd, d, d.dserv)
		if err != nil {
			return nil, err
		}
		d.files[name] = nfi
		return nfi, nil
Jeromy's avatar
Jeromy committed
189
	default:
Jeromy's avatar
Jeromy committed
190
		return nil, fmt.Errorf("unrecognized node type in cache node")
Jeromy's avatar
Jeromy committed
191 192 193
	}
}

Jeromy's avatar
Jeromy committed
194 195 196 197 198 199 200
// Child returns the child of this directory by the given name
func (d *Directory) Child(name string) (FSNode, error) {
	d.lock.Lock()
	defer d.lock.Unlock()
	return d.childUnsync(name)
}

201 202 203 204 205 206 207
func (d *Directory) Uncache(name string) {
	d.lock.Lock()
	defer d.lock.Unlock()
	delete(d.files, name)
	delete(d.childDirs, name)
}

Jeromy's avatar
Jeromy committed
208 209
// childFromDag searches through this directories dag node for a child link
// with the given name
210
func (d *Directory) childFromDag(name string) (ipld.Node, error) {
211
	return d.unixfsDir.Find(d.ctx, name)
212 213
}

Jeromy's avatar
Jeromy committed
214 215
// childUnsync returns the child under this directory by the given name
// without locking, useful for operations which already hold a lock
Jeromy's avatar
Jeromy committed
216
func (d *Directory) childUnsync(name string) (FSNode, error) {
217 218 219
	cdir, ok := d.childDirs[name]
	if ok {
		return cdir, nil
220
	}
221 222 223 224

	cfile, ok := d.files[name]
	if ok {
		return cfile, nil
225 226
	}

227
	return d.childNode(name)
228 229
}

230 231 232 233 234 235 236
type NodeListing struct {
	Name string
	Type int
	Size int64
	Hash string
}

Jeromy's avatar
Jeromy committed
237
func (d *Directory) ListNames(ctx context.Context) ([]string, error) {
238 239
	d.lock.Lock()
	defer d.lock.Unlock()
Jeromy's avatar
Jeromy committed
240

241
	var out []string
242
	err := d.unixfsDir.ForEachLink(ctx, func(l *ipld.Link) error {
243 244 245
		out = append(out, l.Name)
		return nil
	})
246 247 248 249
	if err != nil {
		return nil, err
	}

Jeromy's avatar
Jeromy committed
250
	sort.Strings(out)
Jeromy's avatar
Jeromy committed
251

252
	return out, nil
Jeromy's avatar
Jeromy committed
253 254
}

Jeromy's avatar
Jeromy committed
255
func (d *Directory) List(ctx context.Context) ([]NodeListing, error) {
256
	var out []NodeListing
Jeromy's avatar
Jeromy committed
257
	err := d.ForEachEntry(ctx, func(nl NodeListing) error {
258 259 260 261 262
		out = append(out, nl)
		return nil
	})
	return out, err
}
263

264 265 266
func (d *Directory) ForEachEntry(ctx context.Context, f func(NodeListing) error) error {
	d.lock.Lock()
	defer d.lock.Unlock()
267
	return d.unixfsDir.ForEachLink(ctx, func(l *ipld.Link) error {
268 269
		c, err := d.childUnsync(l.Name)
		if err != nil {
270 271 272 273 274 275 276 277 278 279 280 281
			return err
		}

		nd, err := c.GetNode()
		if err != nil {
			return err
		}

		child := NodeListing{
			Name: l.Name,
			Type: int(c.Type()),
			Hash: nd.Cid().String(),
282 283 284 285 286
		}

		if c, ok := c.(*File); ok {
			size, err := c.Size()
			if err != nil {
287
				return err
288 289 290 291
			}
			child.Size = size
		}

292 293
		return f(child)
	})
294 295 296 297
}

func (d *Directory) Mkdir(name string) (*Directory, error) {
	d.lock.Lock()
Jeromy's avatar
Jeromy committed
298
	defer d.lock.Unlock()
299

Jeromy's avatar
Jeromy committed
300
	fsn, err := d.childUnsync(name)
301
	if err == nil {
Jeromy's avatar
Jeromy committed
302 303 304 305 306 307 308 309
		switch fsn := fsn.(type) {
		case *Directory:
			return fsn, os.ErrExist
		case *File:
			return nil, os.ErrExist
		default:
			return nil, fmt.Errorf("unrecognized type: %#v", fsn)
		}
310 311
	}

312
	ndir := ft.EmptyDirNode()
313
	ndir.SetPrefix(d.GetPrefix())
314

315
	err = d.dserv.Add(d.ctx, ndir)
316 317 318 319
	if err != nil {
		return nil, err
	}

320
	err = d.AddUnixFSChild(name, ndir)
321 322 323 324 325
	if err != nil {
		return nil, err
	}

	dirobj, err := NewDirectory(d.ctx, name, ndir, d, d.dserv)
326 327 328 329
	if err != nil {
		return nil, err
	}

Jeromy's avatar
Jeromy committed
330 331
	d.childDirs[name] = dirobj
	return dirobj, nil
332 333 334 335
}

func (d *Directory) Unlink(name string) error {
	d.lock.Lock()
Jeromy's avatar
Jeromy committed
336 337
	defer d.lock.Unlock()

338 339 340
	delete(d.childDirs, name)
	delete(d.files, name)

341
	return d.unixfsDir.RemoveChild(d.ctx, name)
342 343
}

344
func (d *Directory) Flush() error {
Jeromy's avatar
Jeromy committed
345
	nd, err := d.GetNode()
346 347 348
	if err != nil {
		return err
	}
349

350
	return d.parent.closeChild(d.name, nd, true)
351 352
}

Jeromy's avatar
Jeromy committed
353
// AddChild adds the node 'nd' under this directory giving it the name 'name'
354
func (d *Directory) AddChild(name string, nd ipld.Node) error {
355 356
	d.lock.Lock()
	defer d.lock.Unlock()
357

Jeromy's avatar
Jeromy committed
358
	_, err := d.childUnsync(name)
359
	if err == nil {
360
		return ErrDirExists
361 362
	}

363
	err = d.dserv.Add(d.ctx, nd)
Jeromy's avatar
Jeromy committed
364 365 366 367
	if err != nil {
		return err
	}

368
	err = d.AddUnixFSChild(name, nd)
369 370 371 372
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
373
	d.modTime = time.Now()
Jeromy's avatar
Jeromy committed
374
	return nil
375 376
}

377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
// AddUnixFSChild adds a child to the inner UnixFS directory
// and transitions to a HAMT implementation if needed.
func (d *Directory) AddUnixFSChild(name string, node ipld.Node) error {
	if uio.UseHAMTSharding {
		// If the directory HAMT implementation is being used and this
		// directory is actually a basic implementation switch it to HAMT.
		if basicDir, ok := d.unixfsDir.(*uio.BasicDirectory); ok {
			hamtDir, err := basicDir.SwitchToSharding(d.ctx)
			if err != nil {
				return err
			}
			d.unixfsDir = hamtDir
		}
	}

	err := d.unixfsDir.AddChild(d.ctx, name, node)
	if err != nil {
		return err
	}

	return nil
}

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
func (d *Directory) sync() error {
	for name, dir := range d.childDirs {
		nd, err := dir.GetNode()
		if err != nil {
			return err
		}

		err = d.updateChild(name, nd)
		if err != nil {
			return err
		}
	}

	for name, file := range d.files {
		nd, err := file.GetNode()
		if err != nil {
			return err
		}

		err = d.updateChild(name, nd)
		if err != nil {
			return err
		}
	}

	return nil
}

Jeromy's avatar
Jeromy committed
428 429 430 431
func (d *Directory) Path() string {
	cur := d
	var out string
	for cur != nil {
432 433 434 435 436 437 438 439 440
		switch parent := cur.parent.(type) {
		case *Directory:
			out = path.Join(cur.name, out)
			cur = parent
		case *Root:
			return "/" + out
		default:
			panic("directory parent neither a directory nor a root")
		}
Jeromy's avatar
Jeromy committed
441 442 443 444
	}
	return out
}

445
func (d *Directory) GetNode() (ipld.Node, error) {
446 447
	d.lock.Lock()
	defer d.lock.Unlock()
448 449 450 451 452 453

	err := d.sync()
	if err != nil {
		return nil, err
	}

454
	nd, err := d.unixfsDir.GetNode()
455 456 457 458
	if err != nil {
		return nil, err
	}

459
	err = d.dserv.Add(d.ctx, nd)
460 461 462 463
	if err != nil {
		return nil, err
	}

464
	return nd.Copy(), err
465
}