package inmemory import ( "fmt" "io" "path" "sort" "strings" "time" ) var ( errExists = fmt.Errorf("exists") errNotExists = fmt.Errorf("exists") 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) { if p == "" { 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 srcDirname != strings.TrimSuffix(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 dirname != strings.TrimSuffix(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) { if len(f.data) > 0 && offset >= int64(len(f.data)) { // Extend missing region with a zero pad, while also preallocating out to size of p. pad := offset - int64(len(f.data)) size := len(p) + int(pad) f.data = append(f.data, make([]byte, pad, size)...) } f.data = append(f.data, p...) return len(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 }