package memfs import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "sync" "time" ) type storage struct { files map[string]*file children map[string]map[string]*file } func newStorage() *storage { return &storage{ files: make(map[string]*file, 0), children: make(map[string]map[string]*file, 0), } } func (s *storage) Has(path string) bool { path = clean(path) _, ok := s.files[path] return ok } func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) { path = clean(path) if s.Has(path) { if !s.MustGet(path).mode.IsDir() { return nil, fmt.Errorf("file already exists %q", path) } return nil, nil } name := filepath.Base(path) f := &file{ name: name, content: &content{name: name}, mode: mode, flag: flag, mtime: time.Now(), } s.files[path] = f if err := s.createParent(path, mode, f); err != nil { return nil, err } return f, nil } func (s *storage) createParent(path string, mode os.FileMode, f *file) error { base := filepath.Dir(path) base = clean(base) if f.Name() == string(separator) { return nil } if _, err := s.New(base, mode.Perm()|os.ModeDir, 0); err != nil { return err } if _, ok := s.children[base]; !ok { s.children[base] = make(map[string]*file, 0) } s.children[base][f.Name()] = f return nil } func (s *storage) Children(path string) []*file { path = clean(path) l := make([]*file, 0) for _, f := range s.children[path] { l = append(l, f) } return l } func (s *storage) MustGet(path string) *file { f, ok := s.Get(path) if !ok { panic(fmt.Errorf("couldn't find %q", path)) } return f } func (s *storage) Get(path string) (*file, bool) { path = clean(path) if !s.Has(path) { return nil, false } file, ok := s.files[path] return file, ok } func (s *storage) Rename(from, to string) error { from = clean(from) to = clean(to) if !s.Has(from) { return os.ErrNotExist } move := [][2]string{{from, to}} for pathFrom := range s.files { if pathFrom == from || !strings.HasPrefix(pathFrom, from) { continue } rel, _ := filepath.Rel(from, pathFrom) pathTo := filepath.Join(to, rel) move = append(move, [2]string{pathFrom, pathTo}) } for _, ops := range move { from := ops[0] to := ops[1] if err := s.move(from, to); err != nil { return err } } return nil } func (s *storage) move(from, to string) error { s.files[to] = s.files[from] s.files[to].name = filepath.Base(to) s.children[to] = s.children[from] defer func() { delete(s.children, from) delete(s.files, from) delete(s.children[filepath.Dir(from)], filepath.Base(from)) }() return s.createParent(to, 0644, s.files[to]) } func (s *storage) Remove(path string) error { path = clean(path) f, has := s.Get(path) if !has { return os.ErrNotExist } if f.mode.IsDir() && len(s.children[path]) != 0 { return fmt.Errorf("dir: %s contains files", path) } base, file := filepath.Split(path) base = filepath.Clean(base) delete(s.children[base], file) delete(s.files, path) return nil } func clean(path string) string { return filepath.Clean(filepath.FromSlash(path)) } type content struct { name string bytes []byte m sync.RWMutex } func (c *content) WriteAt(p []byte, off int64) (int, error) { if off < 0 { return 0, &os.PathError{ Op: "writeat", Path: c.name, Err: errors.New("negative offset"), } } c.m.Lock() prev := len(c.bytes) diff := int(off) - prev if diff > 0 { c.bytes = append(c.bytes, make([]byte, diff)...) } c.bytes = append(c.bytes[:off], p...) if len(c.bytes) < prev { c.bytes = c.bytes[:prev] } c.m.Unlock() return len(p), nil } func (c *content) ReadAt(b []byte, off int64) (n int, err error) { if off < 0 { return 0, &os.PathError{ Op: "readat", Path: c.name, Err: errors.New("negative offset"), } } c.m.RLock() size := int64(len(c.bytes)) if off >= size { c.m.RUnlock() return 0, io.EOF } l := int64(len(b)) if off+l > size { l = size - off } btr := c.bytes[off : off+l] n = copy(b, btr) if len(btr) < len(b) { err = io.EOF } c.m.RUnlock() return }