1
0
Эх сурвалжийг харах

рефакторинг, реализовал переименование

kpmy 8 жил өмнө
parent
commit
0a9ab4e4cf
7 өөрчлөгдсөн 481 нэмэгдсэн , 611 устгасан
  1. 26 7
      dav_cmd/main.go
  2. 0 476
      fs.go
  3. 0 29
      fs_test.go
  4. 4 4
      ipfs_api/api.go
  5. 0 16
      memo.go
  6. 0 79
      trav.go
  7. 451 0
      wdfs/fs.go

+ 26 - 7
dav_cmd/main.go

@@ -5,17 +5,35 @@ import (
 	"net/http"
 	"net/url"
 
-	"github.com/kpmy/mipfs"
+	"github.com/kpmy/mipfs/ipfs_api"
+	"github.com/kpmy/mipfs/wdfs"
+	"github.com/peterbourgon/diskv"
 	"golang.org/x/net/webdav"
 )
 
-func init()  {
-	log.SetFlags(0);
+var KV *diskv.Diskv
+
+func init() {
+	log.SetFlags(0)
+
+	KV = diskv.New(diskv.Options{
+		BasePath: ".diskv",
+		Transform: func(s string) []string {
+			return []string{}
+		},
+	})
 }
 
 func main() {
-	fs := mipfs.NewFS()
-	ls := mipfs.NewLS()
+	root := "QmbuSdtGUUfL7DSvvA9DmiGSRqAzkHEjWtsxZDRPBWcawg"
+	if r, err := KV.Read("root"); err == nil {
+		root = string(r)
+	} else {
+		KV.Write("root", []byte(root))
+	}
+	nodeID, _ := ipfs_api.Shell().ID()
+	fs := wdfs.NewFS(nodeID, root)
+	ls := wdfs.NewLS(fs)
 	h := &webdav.Handler{
 		FileSystem: fs,
 		LockSystem: ls,
@@ -27,10 +45,11 @@ func main() {
 					dst = u.Path
 				}
 				o := r.Header.Get("Overwrite")
-				log.Printf("%-20s%-10s%-30s%-30so=%-2s%v", r.Method, r.URL.Path, dst, o, err)
+				log.Println(r.Method, r.URL.Path, dst, o, err)
 			default:
-				log.Printf("%-20s%-10s%-30s%v", r.Method, r.URL.Path, err)
+				log.Println(r.Method, r.URL.Path, err)
 			}
+			log.Println(fs)
 		},
 	}
 	http.Handle("/", h)

+ 0 - 476
fs.go

@@ -1,476 +0,0 @@
-package mipfs
-
-import (
-	"io"
-	"os"
-	"time"
-
-	go_ipfs_api "github.com/ipfs/go-ipfs-api"
-
-	"fmt"
-	"path/filepath"
-	"strings"
-	"sync"
-
-	"github.com/ipfs/go-ipfs/commands/http"
-	"github.com/kpmy/mipfs/ipfs_api"
-	. "github.com/kpmy/ypk/tc"
-	"github.com/mattetti/filebuffer"
-	"golang.org/x/net/webdav"
-	"log"
-	"io/ioutil"
-)
-
-type write struct {
-	pos int64
-	data *filebuffer.Buffer
-}
-
-type file struct {
-	go_ipfs_api.UnixLsLink
-	pos   int64
-	buf   *filebuffer.Buffer
-	links []*go_ipfs_api.LsLink
-	newFn, updFn func(io.ReadCloser)
-	wr chan *write
-	wg *sync.WaitGroup
-}
-
-func (f *file) Name() string {
-	return f.UnixLsLink.Name
-}
-
-func (f *file) Size() int64 {
-	return int64(f.UnixLsLink.Size)
-}
-
-func (f *file) Mode() os.FileMode {
-	return 0
-}
-
-func (f *file) ModTime() time.Time {
-	return time.Now()
-}
-
-func (f *file) IsDir() bool {
-	return false
-}
-func (f *file) Sys() interface{} {
-	return nil
-}
-
-func (f *file) Close() error {
-	if(f.wr != nil) {
-		close(f.wr);
-		f.wg.Wait()
-	}
-	return nil
-}
-
-func (f *file) Read(p []byte) (n int, err error) {
-	if f.links == nil {
-		f.links, _ = ipfs_api.Shell().List(f.Hash)
-	}
-	if len(f.links) == 0 {
-		buf := filebuffer.New(nil)
-		rd, _ := ipfs_api.Shell().Cat(f.Hash)
-		io.Copy(buf, rd)
-		buf.Seek(f.pos, io.SeekStart)
-		return buf.Read(p)
-	} else {
-		var end int64 = 0
-		for _, l := range f.links {
-			begin := end
-			end = begin + int64(l.Size)
-			if begin <= f.pos && f.pos < end {
-				if f.buf == nil {
-					rd, _ := ipfs_api.Shell().Cat(l.Hash)
-					f.buf = filebuffer.New(nil)
-					io.Copy(f.buf, rd)
-					l.Size = uint64(f.buf.Buff.Len())
-				}
-				f.buf.Seek(f.pos-begin, io.SeekStart)
-				n, err = f.buf.Read(p)
-				f.pos = f.pos + int64(n)
-				if f.buf.Index == int64(l.Size) {
-					f.buf = nil
-				}
-				return
-			}
-		}
-		panic(100)
-	}
-}
-
-func (f *file) Seek(offset int64, whence int) (seek int64, err error) {
-	switch whence {
-	case io.SeekStart:
-		f.pos = offset
-	case io.SeekCurrent:
-		f.pos = f.pos + offset
-	case io.SeekEnd:
-		f.pos = f.Size() + offset
-	default:
-		Halt(100)
-	}
-	Assert(f.pos >= 0, 60)
-	seek = f.pos
-	return
-}
-
-func (f *file) Readdir(count int) (ret []os.FileInfo, err error) {
-	panic(100)
-}
-
-func (f *file) Stat() (os.FileInfo, error) {
-	return &link{f.UnixLsLink}, nil
-}
-
-func (f *file) Write(p []byte) (n int, err error) {
-	if (f.wr == nil){
-		f.wr = make(chan *write, 16)
-		f.wg = new(sync.WaitGroup);
-		f.wg.Add(1);
-		go func(f *file) {
-			tmp, _ := ioutil.TempFile(os.TempDir(), "mipfs");
-			for w := range f.wr{
-				tmp.Seek(w.pos, io.SeekStart);
-				w.data.Seek(0, io.SeekStart);
-				io.Copy(tmp, w.data);
-			}
-			tmp.Seek(0, io.SeekStart);
-			if f.newFn != nil {
-				f.newFn(tmp);
-			} else {
-				f.updFn(tmp);
-			}
-			f.wg.Done()
-		}(f);
-	}
-	w := &write{pos: f.pos}
-	w.data = filebuffer.New(nil);
-	n, err = w.data.Write(p);
-	f.wr <- w;
-	f.pos = f.pos + int64(n);
-	return n, nil
-}
-
-type link struct {
-	go_ipfs_api.UnixLsLink
-}
-
-func (l *link) Name() string {
-	return l.UnixLsLink.Name
-}
-
-func (l *link) Size() int64 {
-	return int64(l.UnixLsLink.Size)
-}
-
-func (l *link) Mode() os.FileMode {
-	if l.Type == "Directory" {
-		return os.ModeDir
-	} else if l.Type == "File" {
-		return 0
-	}
-	panic(100)
-}
-
-func (l *link) ModTime() time.Time {
-	return time.Now()
-}
-
-func (l *link) IsDir() bool {
-	return l.Type == "Directory"
-}
-func (l *link) Sys() interface{} {
-	return nil
-}
-
-type loc struct {
-	go_ipfs_api.UnixLsObject
-}
-
-func (l *loc) Name() string {
-	return l.Hash
-}
-
-func (l *loc) Size() int64 {
-	return int64(l.UnixLsObject.Size)
-}
-
-func (l *loc) Mode() os.FileMode {
-	return os.ModeDir
-}
-
-func (l *loc) ModTime() time.Time {
-	return time.Now()
-}
-
-func (l *loc) IsDir() bool {
-	return true
-}
-func (l *loc) Sys() interface{} {
-	return nil
-}
-
-func (l *loc) Close() error {
-	return nil
-}
-
-func (l *loc) Read(p []byte) (n int, err error) {
-	panic(100)
-}
-
-func (l *loc) Seek(offset int64, whence int) (int64, error) {
-	panic(100)
-}
-
-func (l *loc) Readdir(count int) (ret []os.FileInfo, err error) {
-	ls, _ := ipfs_api.Shell().FileList(l.Hash)
-	for _, l := range ls.Links {
-		switch l.Type {
-		case "File":
-			fallthrough
-		case "Directory":
-			ret = append(ret, &link{*l})
-		default:
-			Halt(100)
-		}
-	}
-	return
-}
-
-func (l *loc) Stat() (os.FileInfo, error) {
-	ls, _ := ipfs_api.Shell().FileList(l.Hash)
-	return &loc{*ls}, nil
-}
-
-func (l *loc) Write(p []byte) (n int, err error) {
-	panic(100)
-}
-
-type filesystem struct {
-	webdav.FileSystem
-	node string
-	root string
-}
-
-func (f *filesystem) Mkdir(name string, perm os.FileMode) (err error) {
-	Assert(name != "", 20)
-	ls := split(f.root, name)
-	ns := strings.Split(f.root+name, "/")
-	downHash := ""
-	downPath := ""
-	for i := len(ns) - 1; i >= 0; i-- {
-		newHash := ""
-		if i >= len(ls) {
-			newHash, _ = ipfs_api.Shell().NewObject("unixfs-dir")
-		} else {
-			newHash = ls[i].Hash
-		}
-		if downHash != "" {
-			newHash, _ = ipfs_api.Shell().PatchLink(newHash, downPath, downHash, false)
-		}
-		downHash = newHash
-		downPath = ns[i]
-		if i == 0 {
-			ipfs_api.Shell().Unpin(f.root)
-			f.root = newHash
-			ipfs_api.Shell().Pin(f.root)
-			memo.Write("root", []byte(f.root))
-		}
-	}
-	return
-}
-
-func (f *filesystem) OpenFile(name string, flag int, perm os.FileMode) (webdav.File, error) {
-	log.Println("openfile", name, " ", flag, perm);
-	if li, fi := trav(f.root, name); fi != nil {
-		nf := &file{UnixLsLink: fi.UnixLsLink}
-		switch {
-		case flag&os.O_RDWR != 0:
-			path := filepath.Dir(name)
-			_, last := filepath.Split(name)
-			nf.updFn = func(data io.ReadCloser) {
-				ls := split(f.root, path)
-				ns := strings.Split(strings.Trim(f.root+path, "/"+rootName), rootName)
-				fileHash, _ := ipfs_api.Shell().Add(data)
-				downHash := fileHash
-				downPath := last
-				for i := len(ns) - 1; i >= 0; i-- {
-					newHash := ls[i].Hash
-					newHash, _ = ipfs_api.Shell().PatchLink(newHash, downPath, downHash, false)
-					downHash = newHash
-					downPath = ns[i]
-					if i == 0 {
-						ipfs_api.Shell().Unpin(f.root)
-						f.root = newHash
-						ipfs_api.Shell().Pin(f.root)
-						memo.Write("root", []byte(f.root))
-					}
-				}
-				_, fi := trav(f.root, name)
-				Assert(fi != nil, 60)
-				nf.updFn = nil
-				nf.UnixLsLink = fi.UnixLsLink
-			}
-		}
-		return nf, nil;
-	} else if li != nil {
-		return li, nil
-	} else {
-		switch {
-		case flag&os.O_CREATE != 0:
-			path := filepath.Dir(name)
-			_, last := filepath.Split(name)
-			nf := &file{}
-			nf.newFn = func(data io.ReadCloser) {
-				ls := split(f.root, path)
-				ns := strings.Split(strings.Trim(f.root+path, "/"+rootName), rootName)
-				fileHash, _ := ipfs_api.Shell().Add(data)
-				downHash := fileHash
-				downPath := last
-				for i := len(ns) - 1; i >= 0; i-- {
-					newHash := ls[i].Hash
-					newHash, _ = ipfs_api.Shell().PatchLink(newHash, downPath, downHash, false)
-					downHash = newHash
-					downPath = ns[i]
-					if i == 0 {
-						ipfs_api.Shell().Unpin(f.root)
-						f.root = newHash
-						ipfs_api.Shell().Pin(f.root)
-						memo.Write("root", []byte(f.root))
-					}
-				}
-				_, fi := trav(f.root, name)
-				Assert(fi != nil, 60)
-				nf.newFn = nil
-				nf.UnixLsLink = fi.UnixLsLink
-			}
-			return nf, nil
-		default:
-			log.Println(100, name, " ", flag, perm)
-			return nil, os.ErrNotExist
-		}
-	}
-	return nil, http.ErrNotFound
-}
-
-func (f *filesystem) RemoveAll(name string) (err error) {
-	var ls []*loc
-	var ns []string
-	var newHash string
-	if li, fi := trav(f.root, name); fi != nil {
-		ls = split(f.root, filepath.Dir(name))
-		ns = strings.Split(f.root+filepath.Dir(name), "/")
-		_, fn := filepath.Split(name)
-		newHash, _ = ipfs_api.Shell().Patch(ls[len(ls)-1].Hash, "rm-link", fn)
-		if j := len(ls) - 2; j > 0 {
-			newHash, _ = ipfs_api.Shell().Patch(ls[len(ls)-2].Hash, "rm-link", ns[len(ns)-1])
-		}
-	} else if li != nil {
-		ls = split(f.root, name)
-		ns = strings.Split(f.root+name, "/")
-		newHash, _ = ipfs_api.Shell().Patch(ls[len(ls)-2].Hash, "rm-link", ns[len(ns)-1])
-		Assert(len(ls) > 1 && len(ns) > 1 && len(ls) == len(ns), 20)
-	} else {
-		panic(0)
-	}
-	if j := len(ls) - 2; j > 0 {
-		for i := j - 1; i >= 0; i-- {
-			newHash, _ = ipfs_api.Shell().PatchLink(ls[i].Hash, ns[i+1], newHash, false)
-		}
-	}
-	ipfs_api.Shell().Unpin(f.root)
-	f.root = newHash
-	ipfs_api.Shell().Pin(f.root)
-	memo.Write("root", []byte(f.root))
-	return
-}
-
-func (f *filesystem) Rename(oldName, newName string) error {
-	panic(100)
-}
-
-func (f *filesystem) Stat(name string) (os.FileInfo, error) {
-	if li, fi := trav(f.root, name); fi != nil {
-		return fi, nil
-	} else if li != nil {
-		return li, nil
-	}
-	return nil, os.ErrNotExist
-}
-
-var nodeID *go_ipfs_api.IdOutput
-
-func init() {
-	nodeID, _ = ipfs_api.Shell().ID()
-}
-
-func NewFS() webdav.FileSystem {
-	//root, _ := ipfs.Shell().Resolve(nodeID.ID)
-	root := "QmbuSdtGUUfL7DSvvA9DmiGSRqAzkHEjWtsxZDRPBWcawg"
-	if r, err := memo.Read("root"); err == nil {
-		root = string(r)
-	} else {
-		memo.Write("root", []byte(root))
-	}
-	return &filesystem{node: nodeID.ID, root: root}
-}
-
-type locksystem struct {
-	webdav.LockSystem
-	sync.RWMutex
-	locks  map[string]string
-	tokens map[string]webdav.LockDetails
-}
-
-func (l *locksystem) Confirm(now time.Time, name0, name1 string, conditions ...webdav.Condition) (release func(), err error) {
-	l.RLock()
-	if _, ok := l.locks[name0]; ok{
-		release = func() {
-			log.Println(name0, "release")
-		}
-	} else {
-		err = webdav.ErrConfirmationFailed
-	}
-	l.RUnlock()
-	return
-}
-
-func (l *locksystem) Create(now time.Time, details webdav.LockDetails) (token string, err error) {
-	l.RLock()
-	if _, ok := l.locks[details.Root]; !ok {
-		l.RUnlock()
-		l.Lock()
-		token = fmt.Sprint(now.UnixNano())
-		l.locks[details.Root] = token
-		l.tokens[token] = details
-		l.RWMutex.Unlock()
-	} else {
-		l.RUnlock()
-		err = webdav.ErrLocked
-	}
-	return
-}
-
-func (l *locksystem) Refresh(now time.Time, token string, duration time.Duration) (webdav.LockDetails, error) {
-	panic(100)
-}
-
-func (l *locksystem) Unlock(now time.Time, token string) (err error) {
-	l.Lock()
-	details := l.tokens[token]
-	delete(l.tokens, token)
-	delete(l.locks, details.Root)
-	l.RWMutex.Unlock()
-	return
-}
-
-func NewLS() webdav.LockSystem {
-	ret := &locksystem{}
-	ret.locks = make(map[string]string)
-	ret.tokens = make(map[string]webdav.LockDetails)
-	return ret
-}

+ 0 - 29
fs_test.go

@@ -1,29 +0,0 @@
-package mipfs
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-)
-
-func TestFS(t *testing.T) {
-	NewFS()
-	NewLS()
-}
-
-func TestTrav(t *testing.T) {
-	root := string([]rune{os.PathSeparator})
-	var trav func(string) interface{}
-	trav = func(name string) interface{} {
-		if name == root {
-			return ""
-		} else {
-			_, last := filepath.Split(name)
-			trav(filepath.Dir(name))
-			t.Log(last)
-			return nil
-		}
-	}
-
-	trav("/user/data/pic.gif")
-}

+ 4 - 4
ipfs_api/api.go

@@ -3,14 +3,14 @@ package ipfs_api
 import (
 	"log"
 
-	go_ipfs_api "github.com/ipfs/go-ipfs-api"
+	"github.com/ipfs/go-ipfs-api"
 )
 
-var sh *go_ipfs_api.Shell
+var sh *shell.Shell
 
 func reset() {
 	if sh == nil || !sh.IsUp() {
-		sh = go_ipfs_api.NewShell("127.0.0.1:5001")
+		sh = shell.NewShell("127.0.0.1:5001")
 		if id, err := sh.ID(); err == nil {
 			v0, _, _ := sh.Version()
 			log.Println("ipfs version", v0, "node", id.ID, "online")
@@ -18,7 +18,7 @@ func reset() {
 	}
 }
 
-func Shell() *go_ipfs_api.Shell {
+func Shell() *shell.Shell {
 	reset()
 	return sh
 }

+ 0 - 16
memo.go

@@ -1,16 +0,0 @@
-package mipfs
-
-import (
-	"github.com/peterbourgon/diskv"
-)
-
-var memo *diskv.Diskv
-
-func init() {
-	memo = diskv.New(diskv.Options{
-		BasePath: ".memo",
-		Transform: func(s string) []string {
-			return []string{}
-		},
-	})
-}

+ 0 - 79
trav.go

@@ -1,79 +0,0 @@
-package mipfs
-
-import (
-	"github.com/kpmy/mipfs/ipfs_api"
-	"log"
-	"os"
-	"path/filepath"
-)
-
-var rootName = string([]rune{os.PathSeparator})
-
-func find(li *loc, name string) (ret *loc) {
-	for _, i := range li.Links {
-		if i.Type == "Directory" && i.Name == name {
-			ls, _ := ipfs_api.Shell().FileList(i.Hash)
-			ret = &loc{*ls}
-		}
-	}
-	return
-}
-
-func find2(li *loc, name string) (ret *link) {
-	for _, i := range li.Links {
-		if i.Type == "File" && i.Name == name {
-			ret = &link{*i}
-		}
-	}
-	return
-}
-
-func trav(root string, name string) (*loc, *link) {
-	if name == rootName || name == "/" {
-		if ls, err := ipfs_api.Shell().FileList(root); err != nil {
-			log.Fatal(err)
-			panic(100)
-		} else {
-			return &loc{*ls}, nil
-		}
-	} else {
-		_, last := filepath.Split(name)
-		l, _ := trav(root, filepath.Dir(name))
-		if li := find(l, last); li != nil {
-			return li, nil
-		} else {
-			if f := find2(l, last); f != nil {
-				return l, f
-			} else {
-				return nil, nil
-			}
-		}
-	}
-}
-
-func split(rootHash string, path string) (ret []*loc) {
-	var tr func(root string) *loc
-	tr = func(root string) *loc {
-		if root == rootName || root == "/" {
-			ls, _ := ipfs_api.Shell().FileList(rootHash)
-			l := &loc{*ls}
-			ret = append(ret, l)
-			return l
-		} else {
-			_, last := filepath.Split(root)
-			l := tr(filepath.Dir(root))
-			if l != nil {
-				if li := find(l, last); li != nil {
-					ret = append(ret, li)
-					return li
-				} else {
-					return nil
-				}
-			} else {
-				return nil
-			}
-		}
-	}
-	tr(path)
-	return
-}

+ 451 - 0
wdfs/fs.go

@@ -0,0 +1,451 @@
+package wdfs
+
+import (
+	"fmt"
+	"github.com/ipfs/go-ipfs-api"
+	"github.com/kpmy/mipfs/ipfs_api"
+	. "github.com/kpmy/ypk/tc"
+	"github.com/mattetti/filebuffer"
+	"golang.org/x/net/webdav"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+	"sync"
+	"time"
+)
+
+type loc struct {
+	ch *chain
+}
+
+func (l *loc) Close() error {
+	return nil
+}
+
+func (l *loc) Read(p []byte) (n int, err error) {
+	return 0, webdav.ErrForbidden
+}
+
+func (l *loc) Seek(offset int64, whence int) (int64, error) {
+	return 0, webdav.ErrForbidden
+}
+
+func (l *loc) Readdir(count int) (ret []os.FileInfo, err error) {
+	ls, _ := ipfs_api.Shell().FileList(l.ch.Hash)
+	for _, lo := range ls.Links {
+		switch lo.Type {
+		case "File":
+			fallthrough
+		case "Directory":
+			filepath := l.ch.Hash + "/" + lo.Name
+			ret = append(ret, newChain(l.ch, filepath).tail())
+		default:
+			Halt(100)
+		}
+	}
+	return
+}
+
+func (l *loc) Stat() (os.FileInfo, error) {
+	return l.ch, nil
+}
+
+func (l *loc) Write(p []byte) (n int, err error) {
+	return 0, webdav.ErrForbidden
+}
+
+type block struct {
+	pos  int64
+	data *filebuffer.Buffer
+}
+
+type file struct {
+	ch    *chain
+	pos   int64
+	links []*shell.LsLink
+	buf   *filebuffer.Buffer
+
+	wr chan *block
+	wg *sync.WaitGroup
+}
+
+func (f *file) Name() string {
+	return f.ch.name
+}
+
+func (f *file) Size() int64 {
+	return f.ch.Size()
+}
+
+func (f *file) Mode() os.FileMode {
+	return 0
+}
+
+func (f *file) ModTime() time.Time {
+	return time.Now()
+}
+
+func (f *file) IsDir() bool {
+	return false
+}
+
+func (f *file) Sys() interface{} {
+	return nil
+}
+
+func (f *file) Close() error {
+	if f.wr != nil {
+		close(f.wr)
+		f.wg.Wait()
+	}
+	return nil
+}
+
+func (f *file) Read(p []byte) (n int, err error) {
+	if f.links == nil {
+		f.links, _ = ipfs_api.Shell().List(f.ch.Hash)
+	}
+	if len(f.links) == 0 {
+		buf := filebuffer.New(nil)
+		rd, _ := ipfs_api.Shell().Cat(f.ch.Hash)
+		io.Copy(buf, rd)
+		buf.Seek(f.pos, io.SeekStart)
+		return buf.Read(p)
+	} else {
+		var end int64 = 0
+		for _, l := range f.links {
+			begin := end
+			end = begin + int64(l.Size)
+			if begin <= f.pos && f.pos < end {
+				if f.buf == nil {
+					rd, _ := ipfs_api.Shell().Cat(l.Hash)
+					f.buf = filebuffer.New(nil)
+					io.Copy(f.buf, rd)
+					l.Size = uint64(f.buf.Buff.Len())
+				}
+				f.buf.Seek(f.pos-begin, io.SeekStart)
+				n, err = f.buf.Read(p)
+				f.pos = f.pos + int64(n)
+				if f.buf.Index == int64(l.Size) {
+					f.buf = nil
+				}
+				return
+			}
+		}
+		panic(100)
+	}
+}
+
+func (f *file) Seek(offset int64, whence int) (seek int64, err error) {
+	switch whence {
+	case io.SeekStart:
+		f.pos = offset
+	case io.SeekCurrent:
+		f.pos = f.pos + offset
+	case io.SeekEnd:
+		f.pos = f.Size() + offset
+	default:
+		Halt(100)
+	}
+	Assert(f.pos >= 0, 60)
+	seek = f.pos
+	return
+}
+
+func (f *file) Readdir(count int) (ret []os.FileInfo, err error) {
+	return nil, webdav.ErrForbidden
+}
+
+func (f *file) Stat() (os.FileInfo, error) {
+	return f, nil
+}
+
+func (f *file) update(data io.ReadCloser) {
+	f.ch.Hash, _ = ipfs_api.Shell().Add(data)
+	for tail := f.ch.up; tail != nil; tail = tail.up {
+		tail.Hash, _ = ipfs_api.Shell().PatchLink(tail.Hash, tail.down.name, tail.down.Hash, false)
+	}
+	head := f.ch.head()
+	head.link.Hash = head.Hash
+}
+
+func (f *file) Write(p []byte) (n int, err error) {
+	if f.wr == nil {
+		f.wr = make(chan *block, 16)
+		f.wg = new(sync.WaitGroup)
+		f.wg.Add(1)
+		go func(f *file) {
+			tmp, _ := ioutil.TempFile(os.TempDir(), "mipfs")
+			for w := range f.wr {
+				tmp.Seek(w.pos, io.SeekStart)
+				w.data.Seek(0, io.SeekStart)
+				io.Copy(tmp, w.data)
+			}
+			tmp.Seek(0, io.SeekStart)
+			f.update(tmp)
+			f.wg.Done()
+		}(f)
+	}
+	b := &block{pos: f.pos}
+	b.data = filebuffer.New(nil)
+	n, err = b.data.Write(p)
+	f.wr <- b
+	f.pos = f.pos + int64(n)
+	return n, nil
+}
+
+type chain struct {
+	up, down, link *chain
+	shell.UnixLsObject
+	name string
+}
+
+func newChain(root *chain, filepath string) (ret *chain) {
+	ns := strings.Split(strings.Trim(filepath, "/"), "/")
+	Assert(ns[0] == root.Hash, 20)
+	ret = root
+	for i := 1; i < len(ns); i++ {
+		down := &chain{}
+		down.name = ns[i]
+		down.up, root.down = root, down
+		root = down
+	}
+	skip := false
+	for root = ret; root != nil; root = root.down {
+		if !skip {
+			if o, err := ipfs_api.Shell().FileList(root.Hash); err == nil {
+				root.UnixLsObject = *o
+				if root.down != nil {
+					skip = true
+					for _, l := range o.Links {
+						if l.Name == root.down.name {
+							skip = false
+							root.down.Hash = l.Hash
+						}
+					}
+				}
+			} else {
+				Halt(100, root.name, err)
+			}
+		}
+	}
+	return
+}
+
+func (root *chain) tail() (ret *chain) {
+	for ret = root; ret.down != nil; ret = ret.down {
+	}
+	return
+}
+
+func (root *chain) head() (ret *chain) {
+	for ret = root; ret.up != nil; ret = ret.up {
+	}
+	return
+}
+
+func (root *chain) exists() bool {
+	return root.Hash != ""
+}
+
+func (root *chain) depth() (ret int) {
+	for tail := root; tail != nil; tail = tail.up {
+		ret++
+	}
+	return
+}
+
+func (root *chain) mirror() (ret *chain) {
+	Assert(root.up == nil && root.down == nil, 20)
+	ret = &chain{}
+	ret.Hash = root.Hash
+	ret.link = root
+	return
+}
+
+func (c *chain) Name() string {
+	return c.name
+}
+
+func (c *chain) Size() int64 {
+	return int64(c.UnixLsObject.Size)
+}
+
+func (c *chain) Mode() os.FileMode {
+	if c.Type == "Directory" {
+		return os.ModeDir
+	} else if c.Type == "File" {
+		return 0
+	}
+	panic(100)
+}
+
+func (c *chain) ModTime() time.Time {
+	return time.Now()
+}
+
+func (c *chain) IsDir() bool {
+	return c.Mode() == os.ModeDir
+}
+func (c *chain) Sys() interface{} {
+	return nil
+}
+
+type filesystem struct {
+	webdav.FileSystem
+	nodeId *shell.IdOutput
+	root   *chain
+}
+
+func (f *filesystem) Mkdir(name string, perm os.FileMode) (err error) {
+	chain := newChain(f.root.mirror(), f.root.Hash+name)
+	if tail := chain.tail(); !tail.exists() {
+		for tail != nil {
+			if !tail.exists() {
+				tail.Hash, _ = ipfs_api.Shell().NewObject("unixfs-dir")
+			}
+			if tail.down != nil {
+				tail.Hash, _ = ipfs_api.Shell().PatchLink(tail.Hash, tail.down.name, tail.down.Hash, false)
+			}
+			tail = tail.up
+		}
+		chain.link.Hash = chain.Hash
+	} else {
+		err = os.ErrExist
+	}
+	return
+}
+
+func (f *filesystem) OpenFile(name string, flag int, perm os.FileMode) (ret webdav.File, err error) {
+	chain := newChain(f.root.mirror(), f.root.Hash+name)
+	switch tail := chain.tail(); {
+	case tail.exists() && tail.IsDir():
+		ret = &loc{ch: tail}
+	case tail.exists() && !tail.IsDir():
+		ret = &file{ch: tail}
+	case !tail.exists() && flag&os.O_CREATE != 0:
+		ret = &file{ch: tail}
+	default:
+		log.Println(name, flag, perm)
+		err = os.ErrNotExist
+	}
+	return
+}
+
+func (f *filesystem) RemoveAll(name string) (err error) {
+	chain := newChain(f.root.mirror(), f.root.Hash+name)
+	if tail := chain.tail(); tail.exists() {
+		tail = tail.up
+		tail.Hash, _ = ipfs_api.Shell().Patch(tail.Hash, "rm-link", tail.down.name)
+		tail = tail.up
+		for tail != nil {
+			tail.Hash, _ = ipfs_api.Shell().PatchLink(tail.Hash, tail.down.name, tail.down.Hash, false)
+			tail = tail.up
+		}
+		chain.link.Hash = chain.Hash
+	}
+	return
+}
+
+func (f *filesystem) Rename(oldName, newName string) (err error) {
+	on := newChain(f.root.mirror(), f.root.Hash+oldName)
+	nn := newChain(f.root.mirror(), f.root.Hash+newName)
+	if ot := on.tail(); ot.exists() {
+		if nt := nn.tail(); !nt.exists() {
+			Assert(ot.depth() == nt.depth(), 40)
+			tail := ot.up
+			tail.Hash, _ = ipfs_api.Shell().Patch(tail.Hash, "rm-link", ot.name)
+			tail.Hash, _ = ipfs_api.Shell().PatchLink(tail.Hash, nt.name, ot.Hash, false)
+			tail = tail.up
+			for tail != nil {
+				tail.Hash, _ = ipfs_api.Shell().PatchLink(tail.Hash, tail.down.name, tail.down.Hash, false)
+				tail = tail.up
+			}
+			on.link.Hash = on.Hash
+		} else {
+			err = os.ErrExist
+		}
+	} else {
+		err = os.ErrNotExist
+	}
+	return
+}
+
+func (f *filesystem) Stat(name string) (fi os.FileInfo, err error) {
+	chain := newChain(f.root.mirror(), f.root.Hash+name)
+	tail := chain.tail()
+	if fi = tail; !tail.exists() {
+		err = os.ErrNotExist
+	}
+	return
+}
+
+func (f *filesystem) String() string {
+	return f.root.Hash
+}
+
+func NewFS(id *shell.IdOutput, root string) webdav.FileSystem {
+	ch := &chain{}
+	ch.Hash = root
+	ch.name = root
+	ch.Type = "Directory"
+	return &filesystem{nodeId: id, root: ch}
+}
+
+type locksystem struct {
+	webdav.LockSystem
+	sync.RWMutex
+	locks  map[string]string
+	tokens map[string]webdav.LockDetails
+}
+
+func (l *locksystem) Confirm(now time.Time, name0, name1 string, conditions ...webdav.Condition) (release func(), err error) {
+	l.RLock()
+	if _, ok := l.locks[name0]; ok {
+		release = func() {
+			log.Println(name0, "release")
+		}
+	} else {
+		err = webdav.ErrConfirmationFailed
+	}
+	l.RUnlock()
+	return
+}
+
+func (l *locksystem) Create(now time.Time, details webdav.LockDetails) (token string, err error) {
+	l.RLock()
+	if _, ok := l.locks[details.Root]; !ok {
+		l.RUnlock()
+		l.Lock()
+		token = fmt.Sprint(now.UnixNano())
+		l.locks[details.Root] = token
+		l.tokens[token] = details
+		l.RWMutex.Unlock()
+	} else {
+		l.RUnlock()
+		err = webdav.ErrLocked
+	}
+	return
+}
+
+func (l *locksystem) Refresh(now time.Time, token string, duration time.Duration) (webdav.LockDetails, error) {
+	panic(100)
+}
+
+func (l *locksystem) Unlock(now time.Time, token string) (err error) {
+	l.Lock()
+	details := l.tokens[token]
+	delete(l.tokens, token)
+	delete(l.locks, details.Root)
+	l.RWMutex.Unlock()
+	return
+}
+
+func NewLS(fs webdav.FileSystem) webdav.LockSystem {
+	ret := &locksystem{}
+	ret.locks = make(map[string]string)
+	ret.tokens = make(map[string]webdav.LockDetails)
+	return ret
+}