dir.go 8.65 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

Steven Allen's avatar
Steven Allen committed
18
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
19
	ipld "gx/ipfs/Qme5bWv7wtjUNGsK2BNGVUFPKiuxWrsqrtvYwCLRw8YFES/go-ipld-format"
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
	dirbuilder *uio.Directory

Jeromy's avatar
Jeromy committed
38 39
	modTime time.Time

40
	name string
41 42
}

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

	return &Directory{
		dserv:      dserv,
		ctx:        ctx,
		name:       name,
		dirbuilder: db,
		parent:     parent,
		childDirs:  make(map[string]*Directory),
		files:      make(map[string]*File),
		modTime:    time.Now(),
	}, nil
63 64
}

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

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

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

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

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

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

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

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

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

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

121 122 123
	return pbnd.Copy().(*dag.ProtoNode), nil
}

124
func (d *Directory) updateChild(name string, nd ipld.Node) error {
125
	err := d.dirbuilder.AddChild(d.ctx, name, nd)
126 127 128 129
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
130 131
	d.modTime = time.Now()

132
	return nil
133 134 135 136 137 138
}

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

139 140 141
// 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
142 143 144 145 146
	nd, err := d.childFromDag(name)
	if err != nil {
		return nil, err
	}

147 148 149 150
	return d.cacheNode(name, nd)
}

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

Jeromy's avatar
Jeromy committed
159
		switch i.GetType() {
160 161 162 163 164 165
		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
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
			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:
181 182 183 184 185 186
		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
187
	default:
Jeromy's avatar
Jeromy committed
188
		return nil, fmt.Errorf("unrecognized node type in cache node")
Jeromy's avatar
Jeromy committed
189 190 191
	}
}

Jeromy's avatar
Jeromy committed
192 193 194 195 196 197 198
// 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)
}

199 200 201 202 203 204 205
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
206 207
// childFromDag searches through this directories dag node for a child link
// with the given name
208
func (d *Directory) childFromDag(name string) (ipld.Node, error) {
209
	return d.dirbuilder.Find(d.ctx, name)
210 211
}

Jeromy's avatar
Jeromy committed
212 213
// 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
214
func (d *Directory) childUnsync(name string) (FSNode, error) {
215 216 217
	cdir, ok := d.childDirs[name]
	if ok {
		return cdir, nil
218
	}
219 220 221 222

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

225
	return d.childNode(name)
226 227
}

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

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

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

Jeromy's avatar
Jeromy committed
248
	sort.Strings(out)
Jeromy's avatar
Jeromy committed
249

250
	return out, nil
Jeromy's avatar
Jeromy committed
251 252
}

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

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

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

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

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

290 291
		return f(child)
	})
292 293 294 295
}

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

Jeromy's avatar
Jeromy committed
298
	fsn, err := d.childUnsync(name)
299
	if err == nil {
Jeromy's avatar
Jeromy committed
300 301 302 303 304 305 306 307
		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)
		}
308 309
	}

310
	ndir := ft.EmptyDirNode()
311
	ndir.SetPrefix(d.GetPrefix())
312

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

318 319 320 321 322 323
	err = d.dirbuilder.AddChild(d.ctx, name, ndir)
	if err != nil {
		return nil, err
	}

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

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

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

336 337 338
	delete(d.childDirs, name)
	delete(d.files, name)

339
	return d.dirbuilder.RemoveChild(d.ctx, name)
340 341
}

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

348
	return d.parent.closeChild(d.name, nd, true)
349 350
}

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

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

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

366
	err = d.dirbuilder.AddChild(d.ctx, name, nd)
367 368 369 370
	if err != nil {
		return err
	}

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

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 400 401 402
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
403 404 405 406
func (d *Directory) Path() string {
	cur := d
	var out string
	for cur != nil {
407 408 409 410 411 412 413 414 415
		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
416 417 418 419
	}
	return out
}

420
func (d *Directory) GetNode() (ipld.Node, error) {
421 422
	d.lock.Lock()
	defer d.lock.Unlock()
423 424 425 426 427 428

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

429 430 431 432 433
	nd, err := d.dirbuilder.GetNode()
	if err != nil {
		return nil, err
	}

434
	err = d.dserv.Add(d.ctx, nd)
435 436 437 438
	if err != nil {
		return nil, err
	}

439
	return nd.Copy(), err
440
}