1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-08-06 03:19:47 +08:00
Files
kubo/mfs/system.go
Jeromy 5474e15e93 blockstore locks return unlocker object now
License: MIT
Signed-off-by: Jeromy <jeromyj@gmail.com>
2016-02-08 14:42:07 -08:00

286 lines
5.5 KiB
Go

// package mfs implements an in memory model of a mutable ipfs filesystem.
//
// It consists of four main structs:
// 1) The Filesystem
// The filesystem serves as a container and entry point for various mfs filesystems
// 2) Root
// Root represents an individual filesystem mounted within the mfs system as a whole
// 3) Directories
// 4) Files
package mfs
import (
"errors"
"sync"
"time"
key "github.com/ipfs/go-ipfs/blocks/key"
dag "github.com/ipfs/go-ipfs/merkledag"
ft "github.com/ipfs/go-ipfs/unixfs"
context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
logging "gx/ipfs/Qmazh5oNUVsDZTs2g59rq8aYQqwpss8tcUWQzor5sCCEuH/go-log"
)
var ErrNotExist = errors.New("no such rootfs")
var log = logging.Logger("mfs")
var ErrIsDirectory = errors.New("error: is a directory")
type childCloser interface {
closeChild(string, *dag.Node, bool) error
}
type NodeType int
const (
TFile NodeType = iota
TDir
)
// FSNode represents any node (directory, root, or file) in the mfs filesystem
type FSNode interface {
GetNode() (*dag.Node, error)
Type() NodeType
Lock()
Unlock()
}
// Root represents the root of a filesystem tree
type Root struct {
// node is the merkledag root
node *dag.Node
// val represents the node. It can either be a File or a Directory
val FSNode
repub *Republisher
dserv dag.DAGService
Type string
}
type PubFunc func(context.Context, key.Key) error
// newRoot creates a new Root and starts up a republisher routine for it
func NewRoot(parent context.Context, ds dag.DAGService, node *dag.Node, pf PubFunc) (*Root, error) {
ndk, err := node.Key()
if err != nil {
return nil, err
}
var repub *Republisher
if pf != nil {
repub = NewRepublisher(parent, pf, time.Millisecond*300, time.Second*3)
repub.setVal(ndk)
go repub.Run()
}
root := &Root{
node: node,
repub: repub,
dserv: ds,
}
pbn, err := ft.FromBytes(node.Data)
if err != nil {
log.Error("IPNS pointer was not unixfs node")
return nil, err
}
switch pbn.GetType() {
case ft.TDirectory:
root.val = NewDirectory(parent, ndk.String(), node, root, ds)
case ft.TFile, ft.TMetadata, ft.TRaw:
fi, err := NewFile(ndk.String(), node, root, ds)
if err != nil {
return nil, err
}
root.val = fi
default:
panic("unrecognized! (NYI)")
}
return root, nil
}
func (kr *Root) GetValue() FSNode {
return kr.val
}
func (kr *Root) Flush() error {
nd, err := kr.GetValue().GetNode()
if err != nil {
return err
}
k, err := nd.Key()
if err != nil {
return err
}
if kr.repub != nil {
kr.repub.Update(k)
}
return nil
}
// closeChild implements the childCloser interface, and signals to the publisher that
// there are changes ready to be published
func (kr *Root) closeChild(name string, nd *dag.Node, sync bool) error {
k, err := kr.dserv.Add(nd)
if err != nil {
return err
}
if kr.repub != nil {
kr.repub.Update(k)
}
return nil
}
func (kr *Root) Close() error {
nd, err := kr.GetValue().GetNode()
if err != nil {
return err
}
k, err := nd.Key()
if err != nil {
return err
}
if kr.repub != nil {
kr.repub.Update(k)
return kr.repub.Close()
}
return nil
}
// Republisher manages when to publish a given entry
type Republisher struct {
TimeoutLong time.Duration
TimeoutShort time.Duration
Publish chan struct{}
pubfunc PubFunc
pubnowch chan chan struct{}
ctx context.Context
cancel func()
lk sync.Mutex
val key.Key
lastpub key.Key
}
func (rp *Republisher) getVal() key.Key {
rp.lk.Lock()
defer rp.lk.Unlock()
return rp.val
}
// NewRepublisher creates a new Republisher object to republish the given root
// using the given short and long time intervals
func NewRepublisher(ctx context.Context, pf PubFunc, tshort, tlong time.Duration) *Republisher {
ctx, cancel := context.WithCancel(ctx)
return &Republisher{
TimeoutShort: tshort,
TimeoutLong: tlong,
Publish: make(chan struct{}, 1),
pubfunc: pf,
pubnowch: make(chan chan struct{}),
ctx: ctx,
cancel: cancel,
}
}
func (p *Republisher) setVal(k key.Key) {
p.lk.Lock()
defer p.lk.Unlock()
p.val = k
}
func (p *Republisher) pubNow() {
select {
case p.pubnowch <- nil:
default:
}
}
func (p *Republisher) WaitPub() {
wait := make(chan struct{})
p.pubnowch <- wait
<-wait
}
func (p *Republisher) Close() error {
err := p.publish(p.ctx)
p.cancel()
return err
}
// Touch signals that an update has occurred since the last publish.
// Multiple consecutive touches may extend the time period before
// the next Publish occurs in order to more efficiently batch updates
func (np *Republisher) Update(k key.Key) {
np.setVal(k)
select {
case np.Publish <- struct{}{}:
default:
}
}
// Run is the main republisher loop
func (np *Republisher) Run() {
for {
select {
case <-np.Publish:
quick := time.After(np.TimeoutShort)
longer := time.After(np.TimeoutLong)
wait:
var pubnowresp chan struct{}
select {
case <-np.ctx.Done():
return
case <-np.Publish:
quick = time.After(np.TimeoutShort)
goto wait
case <-quick:
case <-longer:
case pubnowresp = <-np.pubnowch:
}
err := np.publish(np.ctx)
if pubnowresp != nil {
pubnowresp <- struct{}{}
}
if err != nil {
log.Error("republishRoot error: %s", err)
}
case <-np.ctx.Done():
return
}
}
}
func (np *Republisher) publish(ctx context.Context) error {
np.lk.Lock()
topub := np.val
np.lk.Unlock()
log.Info("Publishing Changes!")
err := np.pubfunc(ctx, topub)
if err != nil {
return err
}
np.lk.Lock()
np.lastpub = topub
np.lk.Unlock()
return nil
}