1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-20 02:21:48 +08:00
Files
kubo/ipnsfs/system.go
Jeromy 94bdce63a7 vendor logging lib update
License: MIT
Signed-off-by: Jeromy <jeromyj@gmail.com>
2015-11-05 15:57:21 -08:00

305 lines
6.9 KiB
Go

// package ipnsfs implements an in memory model of a mutable ipns filesystem,
// to be used by the fuse filesystem.
//
// It consists of four main structs:
// 1) The Filesystem
// The filesystem serves as a container and entry point for the ipns filesystem
// 2) KeyRoots
// KeyRoots represent the root of the keyspace controlled by a given keypair
// 3) Directories
// 4) Files
package ipnsfs
import (
"errors"
"os"
"sync"
"time"
key "github.com/ipfs/go-ipfs/blocks/key"
dag "github.com/ipfs/go-ipfs/merkledag"
namesys "github.com/ipfs/go-ipfs/namesys"
ci "github.com/ipfs/go-ipfs/p2p/crypto"
path "github.com/ipfs/go-ipfs/path"
pin "github.com/ipfs/go-ipfs/pin"
ft "github.com/ipfs/go-ipfs/unixfs"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log"
)
var log = logging.Logger("ipnsfs")
var ErrIsDirectory = errors.New("error: is a directory")
// Filesystem is the writeable fuse filesystem structure
type Filesystem struct {
ctx context.Context
dserv dag.DAGService
nsys namesys.NameSystem
resolver *path.Resolver
pins pin.Pinner
roots map[string]*KeyRoot
}
// NewFilesystem instantiates an ipns filesystem using the given parameters and locally owned keys
func NewFilesystem(ctx context.Context, ds dag.DAGService, nsys namesys.NameSystem, pins pin.Pinner, keys ...ci.PrivKey) (*Filesystem, error) {
roots := make(map[string]*KeyRoot)
fs := &Filesystem{
ctx: ctx,
roots: roots,
nsys: nsys,
dserv: ds,
pins: pins,
resolver: &path.Resolver{DAG: ds},
}
for _, k := range keys {
pkh, err := k.GetPublic().Hash()
if err != nil {
return nil, err
}
root, err := fs.newKeyRoot(ctx, k)
if err != nil {
return nil, err
}
roots[key.Key(pkh).Pretty()] = root
}
return fs, nil
}
func (fs *Filesystem) Close() error {
wg := sync.WaitGroup{}
for _, r := range fs.roots {
wg.Add(1)
go func(r *KeyRoot) {
defer wg.Done()
err := r.Publish(fs.ctx)
if err != nil {
log.Info(err)
return
}
}(r)
}
wg.Wait()
return nil
}
// GetRoot returns the KeyRoot of the given name
func (fs *Filesystem) GetRoot(name string) (*KeyRoot, error) {
r, ok := fs.roots[name]
if ok {
return r, nil
}
return nil, os.ErrNotExist
}
type childCloser interface {
closeChild(string, *dag.Node) error
}
type NodeType int
const (
TFile NodeType = iota
TDir
)
// FSNode represents any node (directory, root, or file) in the ipns filesystem
type FSNode interface {
GetNode() (*dag.Node, error)
Type() NodeType
Lock()
Unlock()
}
// KeyRoot represents the root of a filesystem tree pointed to by a given keypair
type KeyRoot struct {
key ci.PrivKey
name string
// node is the merkledag node pointed to by this keypair
node *dag.Node
// A pointer to the filesystem to access components
fs *Filesystem
// val represents the node pointed to by this key. It can either be a File or a Directory
val FSNode
repub *Republisher
}
// newKeyRoot creates a new KeyRoot for the given key, and starts up a republisher routine
// for it
func (fs *Filesystem) newKeyRoot(parent context.Context, k ci.PrivKey) (*KeyRoot, error) {
hash, err := k.GetPublic().Hash()
if err != nil {
return nil, err
}
name := "/ipns/" + key.Key(hash).String()
root := new(KeyRoot)
root.key = k
root.fs = fs
root.name = name
ctx, cancel := context.WithCancel(parent)
defer cancel()
pointsTo, err := fs.nsys.Resolve(ctx, name)
if err != nil {
err = namesys.InitializeKeyspace(ctx, fs.dserv, fs.nsys, fs.pins, k)
if err != nil {
return nil, err
}
pointsTo, err = fs.nsys.Resolve(ctx, name)
if err != nil {
return nil, err
}
}
mnode, err := fs.resolver.ResolvePath(ctx, pointsTo)
if err != nil {
log.Errorf("Failed to retrieve value '%s' for ipns entry: %s\n", pointsTo, err)
return nil, err
}
root.node = mnode
root.repub = NewRepublisher(root, time.Millisecond*300, time.Second*3)
go root.repub.Run(parent)
pbn, err := ft.FromBytes(mnode.Data)
if err != nil {
log.Error("IPNS pointer was not unixfs node")
return nil, err
}
switch pbn.GetType() {
case ft.TDirectory:
root.val = NewDirectory(ctx, pointsTo.String(), mnode, root, fs)
case ft.TFile, ft.TMetadata, ft.TRaw:
fi, err := NewFile(pointsTo.String(), mnode, root, fs)
if err != nil {
return nil, err
}
root.val = fi
default:
panic("unrecognized! (NYI)")
}
return root, nil
}
func (kr *KeyRoot) GetValue() FSNode {
return kr.val
}
// closeChild implements the childCloser interface, and signals to the publisher that
// there are changes ready to be published
func (kr *KeyRoot) closeChild(name string, nd *dag.Node) error {
kr.repub.Touch()
return nil
}
// Publish publishes the ipns entry associated with this key
func (kr *KeyRoot) Publish(ctx context.Context) error {
child, ok := kr.val.(FSNode)
if !ok {
return errors.New("child of key root not valid type")
}
nd, err := child.GetNode()
if err != nil {
return err
}
// Holding this lock so our child doesnt change out from under us
child.Lock()
k, err := kr.fs.dserv.Add(nd)
if err != nil {
child.Unlock()
return err
}
child.Unlock()
// Dont want to hold the lock while we publish
// otherwise we are holding the lock through a costly
// network operation
kp := path.FromKey(k)
ev := &logging.Metadata{"name": kr.name, "key": kp}
defer log.EventBegin(ctx, "ipnsfsPublishing", ev).Done()
log.Info("ipnsfs publishing %s -> %s", kr.name, kp)
return kr.fs.nsys.Publish(ctx, kr.key, kp)
}
// Republisher manages when to publish the ipns entry associated with a given key
type Republisher struct {
TimeoutLong time.Duration
TimeoutShort time.Duration
Publish chan struct{}
root *KeyRoot
}
// NewRepublisher creates a new Republisher object to republish the given keyroot
// using the given short and long time intervals
func NewRepublisher(root *KeyRoot, tshort, tlong time.Duration) *Republisher {
return &Republisher{
TimeoutShort: tshort,
TimeoutLong: tlong,
Publish: make(chan struct{}, 1),
root: root,
}
}
// 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) Touch() {
select {
case np.Publish <- struct{}{}:
default:
}
}
// Run is the main republisher loop
func (np *Republisher) Run(ctx context.Context) {
for {
select {
case <-np.Publish:
quick := time.After(np.TimeoutShort)
longer := time.After(np.TimeoutLong)
wait:
select {
case <-ctx.Done():
return
case <-np.Publish:
quick = time.After(np.TimeoutShort)
goto wait
case <-quick:
case <-longer:
}
log.Info("Publishing Changes!")
err := np.root.Publish(ctx)
if err != nil {
log.Error("republishRoot error: %s", err)
}
case <-ctx.Done():
return
}
}
}