Bläddra i källkod

инструкция по тестированию с litmus, фиксы после тестирования, заложил механизм аттрибутов файла

kpmy 8 år sedan
förälder
incheckning
bc892c22ee
11 ändrade filer med 567 tillägg och 353 borttagningar
  1. 19 2
      dav_cmd/main.go
  2. 6 0
      dav_test/Dockerfile
  3. 15 0
      dav_test/README.md
  4. 51 0
      ipfs_api/add.go
  5. 18 3
      ipfs_api/api.go
  6. 108 0
      wdfs/chain.go
  7. 173 0
      wdfs/file.go
  8. 28 348
      wdfs/fs.go
  9. 63 0
      wdfs/loc.go
  10. 78 0
      wdfs/ls.go
  11. 8 0
      wdfs/prop.go

+ 19 - 2
dav_cmd/main.go

@@ -9,6 +9,7 @@ import (
 	"github.com/kpmy/mipfs/ipfs_api"
 	"github.com/kpmy/mipfs/wdfs"
 	"github.com/kpmy/ypk/fn"
+	. "github.com/kpmy/ypk/tc"
 	"github.com/peterbourgon/diskv"
 	"golang.org/x/net/webdav"
 	"os"
@@ -30,14 +31,29 @@ func init() {
 func main() {
 	log.Println(os.Getwd())
 	root := "QmbuSdtGUUfL7DSvvA9DmiGSRqAzkHEjWtsxZDRPBWcawg"
-	if r, err := KV.Read("root"); err == nil {
+	if r, err := KV.Read("root"); err == nil && len(r) > 0 {
 		root = string(r)
 	} else {
 		KV.Write("root", []byte(root))
 	}
+
 	if r, err := KV.Read("ipfs"); err == nil {
 		ipfs_api.Addr = string(r)
 	}
+
+	rootCh := make(chan string, 16)
+	go func(ch chan string) {
+		for {
+			for s := range ch {
+				if s != "" {
+					KV.Write("root", []byte(s))
+				} else {
+					Halt(100, "empty root")
+				}
+			}
+		}
+	}(rootCh)
+
 	var fs webdav.FileSystem
 	var ls webdav.LockSystem
 	if nodeID, err := ipfs_api.Shell().ID(); err == nil {
@@ -63,7 +79,8 @@ func main() {
 				default:
 					log.Println(r.Method, r.URL.Path, err)
 				}
-				KV.Write("root", []byte(fmt.Sprint(fs)))
+				//log.Println(fs)
+				rootCh <- fmt.Sprint(fs)
 			},
 		}
 		http.Handle("/ipfs/", h)

+ 6 - 0
dav_test/Dockerfile

@@ -0,0 +1,6 @@
+FROM gcc:latest
+COPY . /usr/src/litmus
+WORKDIR /usr/src/litmus
+RUN ./configure
+RUN make install
+CMD litmus -k http://192.168.1.37:6001/ipfs/

+ 15 - 0
dav_test/README.md

@@ -0,0 +1,15 @@
+# тест litmus
+
+http://www.webdav.org/neon/litmus/
+
+`wget http://www.webdav.org/neon/litmus/litmus-0.13.tar.gz`
+
+распаковать
+
+взять отсюда Dockerfile
+
+`docker build -t kpmy/litmus:0.13 .`
+
+`docker run -it --name litmus kpmy/litmus:0.13`
+
+`docker start -i litmus`...

+ 51 - 0
ipfs_api/add.go

@@ -0,0 +1,51 @@
+package ipfs_api
+
+import (
+	"encoding/json"
+	"io"
+	"io/ioutil"
+
+	"github.com/ipfs/go-ipfs-api"
+	files "github.com/whyrusleeping/go-multipart-files"
+)
+
+type object struct {
+	Hash string
+}
+
+// Add a file to ipfs from the given reader, returns the hash of the added file
+func (s *MyShell) Add(r io.Reader) (string, error) {
+	var rc io.ReadCloser
+	if rclose, ok := r.(io.ReadCloser); ok {
+		rc = rclose
+	} else {
+		rc = ioutil.NopCloser(r)
+	}
+
+	// handler expects an array of files
+	fr := files.NewReaderFile("", "", rc, nil)
+	slf := files.NewSliceFile("", "", []files.File{fr})
+	fileReader := files.NewMultiFileReader(slf, true)
+
+	req := shell.NewRequest(s.Url, "add")
+	req.Body = fileReader
+	req.Opts["progress"] = "false"
+	req.Opts["chunker"] = "size-1048576"
+
+	resp, err := req.Send(s.Client)
+	if err != nil {
+		return "", err
+	}
+	defer resp.Close()
+	if resp.Error != nil {
+		return "", resp.Error
+	}
+
+	var out object
+	err = json.NewDecoder(resp.Output).Decode(&out)
+	if err != nil {
+		return "", err
+	}
+
+	return out.Hash, nil
+}

+ 18 - 3
ipfs_api/api.go

@@ -4,15 +4,30 @@ import (
 	"log"
 
 	"github.com/ipfs/go-ipfs-api"
+	"net/http"
 )
 
-var sh *shell.Shell
+var sh *MyShell
 
 var Addr = "127.0.0.1:5001"
 
+type MyShell struct {
+	shell.Shell
+	Url    string
+	Client *http.Client
+}
+
 func reset() {
 	if sh == nil || !sh.IsUp() {
-		sh = shell.NewShell(Addr)
+		sh = &MyShell{
+			Url: Addr,
+			Client: &http.Client{
+				Transport: &http.Transport{
+					DisableKeepAlives: true,
+				},
+			},
+		}
+		sh.Shell = *shell.NewShellWithClient(sh.Url, sh.Client)
 		if id, err := sh.ID(); err == nil {
 			v0, _, _ := sh.Version()
 			log.Println("ipfs version", v0, "node", id.ID, "online")
@@ -20,7 +35,7 @@ func reset() {
 	}
 }
 
-func Shell() *shell.Shell {
+func Shell() *MyShell {
 	reset()
 	return sh
 }

+ 108 - 0
wdfs/chain.go

@@ -0,0 +1,108 @@
+package wdfs
+
+import (
+	"github.com/ipfs/go-ipfs-api"
+	"github.com/kpmy/mipfs/ipfs_api"
+	. "github.com/kpmy/ypk/tc"
+	"os"
+	"strings"
+	"time"
+)
+
+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{} {
+	Halt(100)
+	return nil
+}

+ 173 - 0
wdfs/file.go

@@ -0,0 +1,173 @@
+package wdfs
+
+import (
+	"encoding/xml"
+	"github.com/ipfs/go-ipfs-api"
+	"github.com/kpmy/mipfs/ipfs_api"
+	"github.com/kpmy/ypk/fn"
+	"github.com/mattetti/filebuffer"
+	"io/ioutil"
+	"os"
+	"sync"
+	"time"
+
+	. "github.com/kpmy/ypk/tc"
+	"golang.org/x/net/webdav"
+	"io"
+	"log"
+)
+
+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{} {
+	Halt(100)
+	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 {
+		if fn.IsNil(f.buf) {
+			f.buf = filebuffer.New(nil)
+			rd, _ := ipfs_api.Shell().Cat(f.ch.Hash)
+			io.Copy(f.buf, rd)
+		}
+		f.buf.Seek(f.pos, io.SeekStart)
+		n, err = f.buf.Read(p)
+		f.pos = f.pos + int64(n)
+		return n, err
+	} 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
+}
+
+func (f *file) DeadProps() (map[xml.Name]webdav.Property, error) {
+	log.Println("file prop get")
+	return nil, nil
+}
+
+func (f *file) Patch(patch []webdav.Proppatch) ([]webdav.Propstat, error) {
+	log.Println("file prop patch", patch)
+	return nil, nil
+}

+ 28 - 348
wdfs/fs.go

@@ -1,303 +1,17 @@
 package wdfs
 
 import (
-	"fmt"
 	"github.com/ipfs/go-ipfs-api"
 	"github.com/kpmy/mipfs/ipfs_api"
+	"github.com/kpmy/ypk/dom"
 	"github.com/kpmy/ypk/fn"
 	. "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 {
-		if fn.IsNil(f.buf) {
-			f.buf = filebuffer.New(nil)
-			rd, _ := ipfs_api.Shell().Cat(f.ch.Hash)
-			io.Copy(f.buf, rd)
-		}
-		f.buf.Seek(f.pos, io.SeekStart)
-		n, err = f.buf.Read(p)
-		f.pos = f.pos + int64(n)
-		return n, err
-	} 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
@@ -307,9 +21,22 @@ type filesystem struct {
 func (f *filesystem) Mkdir(name string, perm os.FileMode) (err error) {
 	chain := newChain(f.root.mirror(), f.root.Hash+"/"+strings.Trim(name, "/"))
 	if tail := chain.tail(); !tail.exists() {
+		onlyOne := true
 		for tail != nil {
 			if !tail.exists() {
-				tail.Hash, _ = ipfs_api.Shell().NewObject("unixfs-dir")
+				if onlyOne {
+					tail.Hash, _ = ipfs_api.Shell().NewObject("unixfs-dir")
+					prop := newProps()
+					propHash, _ := ipfs_api.Shell().Add(dom.EncodeWithHeader(prop))
+					if tail.Hash, err = ipfs_api.Shell().PatchLink(tail.Hash, "*", propHash, false); err != nil {
+						log.Fatal(err)
+						return
+					}
+					onlyOne = false
+				} else {
+					err = os.ErrNotExist
+					return
+				}
 			}
 			if tail.down != nil {
 				tail.Hash, _ = ipfs_api.Shell().PatchLink(tail.Hash, tail.down.name, tail.down.Hash, false)
@@ -324,16 +51,24 @@ func (f *filesystem) Mkdir(name string, perm os.FileMode) (err error) {
 }
 
 func (f *filesystem) OpenFile(name string, flag int, perm os.FileMode) (ret webdav.File, err error) {
-	chain := newChain(f.root.mirror(), f.root.Hash+"/"+strings.Trim(name, "/"))
-	switch tail := chain.tail(); {
+	log.Println("open", name, flag, perm)
+	path := newChain(f.root.mirror(), f.root.Hash+"/"+strings.Trim(name, "/"))
+	switch tail := path.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}
+		var edir *chain
+		for edir = tail.up; edir != nil && edir.exists() && edir.IsDir(); edir = edir.up {
+		}
+		if fn.IsNil(edir) {
+			ret = &file{ch: tail}
+		} else {
+			err = os.ErrNotExist
+		}
 	default:
-		log.Println(name, flag, perm)
+		log.Println("open error", name, flag, perm)
 		err = os.ErrNotExist
 	}
 	return
@@ -379,6 +114,7 @@ func (f *filesystem) Rename(oldName, newName string) (err error) {
 }
 
 func (f *filesystem) Stat(name string) (fi os.FileInfo, err error) {
+	log.Println("stat", name)
 	chain := newChain(f.root.mirror(), f.root.Hash+"/"+strings.Trim(name, "/"))
 	tail := chain.tail()
 	if fi = tail; !tail.exists() {
@@ -398,59 +134,3 @@ func NewFS(id *shell.IdOutput, root string) *filesystem {
 	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) *locksystem {
-	ret := &locksystem{}
-	ret.locks = make(map[string]string)
-	ret.tokens = make(map[string]webdav.LockDetails)
-	return ret
-}

+ 63 - 0
wdfs/loc.go

@@ -0,0 +1,63 @@
+package wdfs
+
+import (
+	"encoding/xml"
+	"github.com/kpmy/mipfs/ipfs_api"
+	. "github.com/kpmy/ypk/tc"
+	"golang.org/x/net/webdav"
+	"log"
+	"os"
+	"strings"
+)
+
+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":
+			if !strings.HasPrefix(lo.Name, "*") {
+				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
+}
+
+func (l *loc) DeadProps() (map[xml.Name]webdav.Property, error) {
+	log.Println("loc prop get")
+	return nil, nil
+}
+
+func (l *loc) Patch(patch []webdav.Proppatch) ([]webdav.Propstat, error) {
+	log.Println("loc prop patch", patch)
+	return nil, nil
+}

+ 78 - 0
wdfs/ls.go

@@ -0,0 +1,78 @@
+package wdfs
+
+import (
+	"fmt"
+	"golang.org/x/net/webdav"
+	"log"
+	"sync"
+	"time"
+)
+
+type locksystem struct {
+	webdav.LockSystem
+	sync.RWMutex
+	locks  map[string]string
+	tokens map[string]webdav.LockDetails
+	idx    chan string
+}
+
+func (l *locksystem) Confirm(now time.Time, name0, name1 string, conditions ...webdav.Condition) (release func(), err error) {
+	log.Println("confirm", name0, name1, conditions)
+	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) {
+	log.Println("lock", details)
+	l.RLock()
+	if _, ok := l.locks[details.Root]; !ok {
+		l.RUnlock()
+		l.Lock()
+		token = <-l.idx + ":" + fmt.Sprint(now.UnixNano())
+		l.locks[details.Root] = token
+		l.tokens[token] = details
+		log.Println("locked", token)
+		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) {
+	log.Println("unlock", token)
+	l.Lock()
+	details := l.tokens[token]
+	delete(l.tokens, token)
+	delete(l.locks, details.Root)
+	l.RWMutex.Unlock()
+	return
+}
+
+func NewLS(fs webdav.FileSystem) *locksystem {
+	ret := &locksystem{}
+	ret.locks = make(map[string]string)
+	ret.tokens = make(map[string]webdav.LockDetails)
+	ret.idx = make(chan string)
+	go func(ch chan string) {
+		i := 0
+		for {
+			ch <- fmt.Sprint(i)
+			i++
+		}
+	}(ret.idx)
+	return ret
+}

+ 8 - 0
wdfs/prop.go

@@ -0,0 +1,8 @@
+package wdfs
+
+import "github.com/kpmy/ypk/dom"
+
+func newProps() (ret dom.Element) {
+	ret = dom.Elem("props")
+	return
+}