334 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			334 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
package inmemory
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"path"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	errExists    = fmt.Errorf("exists")
 | 
						|
	errNotExists = fmt.Errorf("notexists")
 | 
						|
	errIsNotDir  = fmt.Errorf("notdir")
 | 
						|
	errIsDir     = fmt.Errorf("isdir")
 | 
						|
)
 | 
						|
 | 
						|
type node interface {
 | 
						|
	name() string
 | 
						|
	path() string
 | 
						|
	isdir() bool
 | 
						|
	modtime() time.Time
 | 
						|
}
 | 
						|
 | 
						|
// dir is the central type for the memory-based  storagedriver. All operations
 | 
						|
// are dispatched from a root dir.
 | 
						|
type dir struct {
 | 
						|
	common
 | 
						|
 | 
						|
	// TODO(stevvooe): Use sorted slice + search.
 | 
						|
	children map[string]node
 | 
						|
}
 | 
						|
 | 
						|
var _ node = &dir{}
 | 
						|
 | 
						|
func (d *dir) isdir() bool {
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// add places the node n into dir d.
 | 
						|
func (d *dir) add(n node) {
 | 
						|
	if d.children == nil {
 | 
						|
		d.children = make(map[string]node)
 | 
						|
	}
 | 
						|
 | 
						|
	d.children[n.name()] = n
 | 
						|
	d.mod = time.Now()
 | 
						|
}
 | 
						|
 | 
						|
// find searches for the node, given path q in dir. If the node is found, it
 | 
						|
// will be returned. If the node is not found, the closet existing parent. If
 | 
						|
// the node is found, the returned (node).path() will match q.
 | 
						|
func (d *dir) find(q string) node {
 | 
						|
	q = strings.Trim(q, "/")
 | 
						|
	i := strings.Index(q, "/")
 | 
						|
 | 
						|
	if q == "" {
 | 
						|
		return d
 | 
						|
	}
 | 
						|
 | 
						|
	if i == 0 {
 | 
						|
		panic("shouldn't happen, no root paths")
 | 
						|
	}
 | 
						|
 | 
						|
	var component string
 | 
						|
	if i < 0 {
 | 
						|
		// No more path components
 | 
						|
		component = q
 | 
						|
	} else {
 | 
						|
		component = q[:i]
 | 
						|
	}
 | 
						|
 | 
						|
	child, ok := d.children[component]
 | 
						|
	if !ok {
 | 
						|
		// Node was not found. Return p and the current node.
 | 
						|
		return d
 | 
						|
	}
 | 
						|
 | 
						|
	if child.isdir() {
 | 
						|
		// traverse down!
 | 
						|
		q = q[i+1:]
 | 
						|
		return child.(*dir).find(q)
 | 
						|
	}
 | 
						|
 | 
						|
	return child
 | 
						|
}
 | 
						|
 | 
						|
func (d *dir) list(p string) ([]string, error) {
 | 
						|
	n := d.find(p)
 | 
						|
 | 
						|
	if n.path() != p {
 | 
						|
		return nil, errNotExists
 | 
						|
	}
 | 
						|
 | 
						|
	if !n.isdir() {
 | 
						|
		return nil, errIsNotDir
 | 
						|
	}
 | 
						|
 | 
						|
	var children []string
 | 
						|
	for _, child := range n.(*dir).children {
 | 
						|
		children = append(children, child.path())
 | 
						|
	}
 | 
						|
 | 
						|
	sort.Strings(children)
 | 
						|
	return children, nil
 | 
						|
}
 | 
						|
 | 
						|
// mkfile or return the existing one. returns an error if it exists and is a
 | 
						|
// directory. Essentially, this is open or create.
 | 
						|
func (d *dir) mkfile(p string) (*file, error) {
 | 
						|
	n := d.find(p)
 | 
						|
	if n.path() == p {
 | 
						|
		if n.isdir() {
 | 
						|
			return nil, errIsDir
 | 
						|
		}
 | 
						|
 | 
						|
		return n.(*file), nil
 | 
						|
	}
 | 
						|
 | 
						|
	dirpath, filename := path.Split(p)
 | 
						|
	// Make any non-existent directories
 | 
						|
	n, err := d.mkdirs(dirpath)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	dd := n.(*dir)
 | 
						|
	n = &file{
 | 
						|
		common: common{
 | 
						|
			p:   path.Join(dd.path(), filename),
 | 
						|
			mod: time.Now(),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	dd.add(n)
 | 
						|
	return n.(*file), nil
 | 
						|
}
 | 
						|
 | 
						|
// mkdirs creates any missing directory entries in p and returns the result.
 | 
						|
func (d *dir) mkdirs(p string) (*dir, error) {
 | 
						|
	p = normalize(p)
 | 
						|
 | 
						|
	n := d.find(p)
 | 
						|
 | 
						|
	if !n.isdir() {
 | 
						|
		// Found something there
 | 
						|
		return nil, errIsNotDir
 | 
						|
	}
 | 
						|
 | 
						|
	if n.path() == p {
 | 
						|
		return n.(*dir), nil
 | 
						|
	}
 | 
						|
 | 
						|
	dd := n.(*dir)
 | 
						|
 | 
						|
	relative := strings.Trim(strings.TrimPrefix(p, n.path()), "/")
 | 
						|
 | 
						|
	if relative == "" {
 | 
						|
		return dd, nil
 | 
						|
	}
 | 
						|
 | 
						|
	components := strings.Split(relative, "/")
 | 
						|
	for _, component := range components {
 | 
						|
		d, err := dd.mkdir(component)
 | 
						|
 | 
						|
		if err != nil {
 | 
						|
			// This should actually never happen, since there are no children.
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		dd = d
 | 
						|
	}
 | 
						|
 | 
						|
	return dd, nil
 | 
						|
}
 | 
						|
 | 
						|
// mkdir creates a child directory under d with the given name.
 | 
						|
func (d *dir) mkdir(name string) (*dir, error) {
 | 
						|
	if name == "" {
 | 
						|
		return nil, fmt.Errorf("invalid dirname")
 | 
						|
	}
 | 
						|
 | 
						|
	_, ok := d.children[name]
 | 
						|
	if ok {
 | 
						|
		return nil, errExists
 | 
						|
	}
 | 
						|
 | 
						|
	child := &dir{
 | 
						|
		common: common{
 | 
						|
			p:   path.Join(d.path(), name),
 | 
						|
			mod: time.Now(),
 | 
						|
		},
 | 
						|
	}
 | 
						|
	d.add(child)
 | 
						|
	d.mod = time.Now()
 | 
						|
 | 
						|
	return child, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *dir) move(src, dst string) error {
 | 
						|
	dstDirname, _ := path.Split(dst)
 | 
						|
 | 
						|
	dp, err := d.mkdirs(dstDirname)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	srcDirname, srcFilename := path.Split(src)
 | 
						|
	sp := d.find(srcDirname)
 | 
						|
 | 
						|
	if normalize(srcDirname) != normalize(sp.path()) {
 | 
						|
		return errNotExists
 | 
						|
	}
 | 
						|
 | 
						|
	s, ok := sp.(*dir).children[srcFilename]
 | 
						|
	if !ok {
 | 
						|
		return errNotExists
 | 
						|
	}
 | 
						|
 | 
						|
	delete(sp.(*dir).children, srcFilename)
 | 
						|
 | 
						|
	switch n := s.(type) {
 | 
						|
	case *dir:
 | 
						|
		n.p = dst
 | 
						|
	case *file:
 | 
						|
		n.p = dst
 | 
						|
	}
 | 
						|
 | 
						|
	dp.add(s)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *dir) delete(p string) error {
 | 
						|
	dirname, filename := path.Split(p)
 | 
						|
	parent := d.find(dirname)
 | 
						|
 | 
						|
	if normalize(dirname) != normalize(parent.path()) {
 | 
						|
		return errNotExists
 | 
						|
	}
 | 
						|
 | 
						|
	if _, ok := parent.(*dir).children[filename]; !ok {
 | 
						|
		return errNotExists
 | 
						|
	}
 | 
						|
 | 
						|
	delete(parent.(*dir).children, filename)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// dump outputs a primitive directory structure to stdout.
 | 
						|
func (d *dir) dump(indent string) {
 | 
						|
	fmt.Println(indent, d.name()+"/")
 | 
						|
 | 
						|
	for _, child := range d.children {
 | 
						|
		if child.isdir() {
 | 
						|
			child.(*dir).dump(indent + "\t")
 | 
						|
		} else {
 | 
						|
			fmt.Println(indent, child.name())
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (d *dir) String() string {
 | 
						|
	return fmt.Sprintf("&dir{path: %v, children: %v}", d.p, d.children)
 | 
						|
}
 | 
						|
 | 
						|
// file stores actual data in the fs tree. It acts like an open, seekable file
 | 
						|
// where operations are conducted through ReadAt and WriteAt. Use it with
 | 
						|
// SectionReader for the best effect.
 | 
						|
type file struct {
 | 
						|
	common
 | 
						|
	data []byte
 | 
						|
}
 | 
						|
 | 
						|
var _ node = &file{}
 | 
						|
 | 
						|
func (f *file) isdir() bool {
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) truncate() {
 | 
						|
	f.data = f.data[:0]
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) sectionReader(offset int64) io.Reader {
 | 
						|
	return io.NewSectionReader(f, offset, int64(len(f.data))-offset)
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) ReadAt(p []byte, offset int64) (n int, err error) {
 | 
						|
	return copy(p, f.data[offset:]), nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) WriteAt(p []byte, offset int64) (n int, err error) {
 | 
						|
	off := int(offset)
 | 
						|
	if cap(f.data) < off+len(p) {
 | 
						|
		data := make([]byte, len(f.data), off+len(p))
 | 
						|
		copy(data, f.data)
 | 
						|
		f.data = data
 | 
						|
	}
 | 
						|
 | 
						|
	f.mod = time.Now()
 | 
						|
	f.data = f.data[:off+len(p)]
 | 
						|
 | 
						|
	return copy(f.data[off:off+len(p)], p), nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) String() string {
 | 
						|
	return fmt.Sprintf("&file{path: %q}", f.p)
 | 
						|
}
 | 
						|
 | 
						|
// common provides shared fields and methods for node implementations.
 | 
						|
type common struct {
 | 
						|
	p   string
 | 
						|
	mod time.Time
 | 
						|
}
 | 
						|
 | 
						|
func (c *common) name() string {
 | 
						|
	_, name := path.Split(c.p)
 | 
						|
	return name
 | 
						|
}
 | 
						|
 | 
						|
func (c *common) path() string {
 | 
						|
	return c.p
 | 
						|
}
 | 
						|
 | 
						|
func (c *common) modtime() time.Time {
 | 
						|
	return c.mod
 | 
						|
}
 | 
						|
 | 
						|
func normalize(p string) string {
 | 
						|
	return "/" + strings.Trim(p, "/")
 | 
						|
}
 |