| // Package memfs provides a billy filesystem base on memory. |
| package memfs // import "gopkg.in/src-d/go-billy.v2/memfs" |
| |
| import ( |
| "errors" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "time" |
| |
| "gopkg.in/src-d/go-billy.v2" |
| ) |
| |
| const separator = filepath.Separator |
| |
| // Memory a very convenient filesystem based on memory files |
| type Memory struct { |
| base string |
| s *storage |
| |
| tempCount int |
| } |
| |
| //New returns a new Memory filesystem |
| func New() *Memory { |
| return &Memory{ |
| base: string(separator), |
| s: newStorage(), |
| } |
| } |
| |
| // Create returns a new file in memory from a given filename. |
| func (fs *Memory) Create(filename string) (billy.File, error) { |
| return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) |
| } |
| |
| // Open returns a readonly file from a given name. |
| func (fs *Memory) Open(filename string) (billy.File, error) { |
| return fs.OpenFile(filename, os.O_RDONLY, 0) |
| } |
| |
| // OpenFile returns the file from a given name with given flag and permits. |
| func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { |
| path := fs.Join(fs.base, filename) |
| |
| f, has := fs.s.Get(path) |
| if !has { |
| if !isCreate(flag) { |
| return nil, os.ErrNotExist |
| } |
| |
| var err error |
| f, err = fs.s.New(path, perm, flag) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if f.mode.IsDir() { |
| return nil, fmt.Errorf("cannot open directory: %s", filename) |
| } |
| |
| filename, err := filepath.Rel(fs.base, path) |
| if err != nil { |
| return nil, err |
| } |
| |
| return f.Duplicate(filename, perm, flag), nil |
| } |
| |
| // Stat returns a billy.FileInfo with the information of the requested file. |
| func (fs *Memory) Stat(filename string) (billy.FileInfo, error) { |
| fullpath := fs.Join(fs.base, filename) |
| |
| f, has := fs.s.Get(fullpath) |
| if !has { |
| return nil, os.ErrNotExist |
| } |
| |
| return f.Stat(), nil |
| } |
| |
| // ReadDir returns a list of billy.FileInfo in the given directory. |
| func (fs *Memory) ReadDir(path string) ([]billy.FileInfo, error) { |
| path = fs.Join(fs.base, path) |
| |
| var entries []billy.FileInfo |
| for _, f := range fs.s.Children(path) { |
| entries = append(entries, f.Stat()) |
| } |
| |
| return entries, nil |
| } |
| |
| // MkdirAll creates a directory. |
| func (fs *Memory) MkdirAll(path string, perm os.FileMode) error { |
| fullpath := fs.Join(fs.base, path) |
| |
| _, err := fs.s.New(fullpath, perm|os.ModeDir, 0) |
| return err |
| } |
| |
| var maxTempFiles = 1024 * 4 |
| |
| // TempFile creates a new temporary file. |
| func (fs *Memory) TempFile(dir, prefix string) (billy.File, error) { |
| var fullpath string |
| for { |
| if fs.tempCount >= maxTempFiles { |
| return nil, errors.New("max. number of tempfiles reached") |
| } |
| |
| fullpath = fs.getTempFilename(dir, prefix) |
| if _, ok := fs.s.files[fullpath]; !ok { |
| break |
| } |
| } |
| |
| return fs.Create(fullpath) |
| } |
| |
| func (fs *Memory) getTempFilename(dir, prefix string) string { |
| fs.tempCount++ |
| filename := fmt.Sprintf("%s_%d_%d", prefix, fs.tempCount, time.Now().UnixNano()) |
| return fs.Join(fs.base, dir, filename) |
| } |
| |
| // Rename moves a the `from` file to the `to` file. |
| func (fs *Memory) Rename(from, to string) error { |
| from = fs.Join(fs.base, from) |
| to = fs.Join(fs.base, to) |
| |
| return fs.s.Rename(from, to) |
| } |
| |
| // Remove deletes a given file from storage. |
| func (fs *Memory) Remove(filename string) error { |
| fullpath := fs.Join(fs.base, filename) |
| return fs.s.Remove(fullpath) |
| } |
| |
| // Join joins any number of path elements into a single path, adding a Separator if necessary. |
| func (fs *Memory) Join(elem ...string) string { |
| return filepath.Join(elem...) |
| } |
| |
| // Dir creates a new memory filesystem whose root is the given path inside the current |
| // filesystem. |
| func (fs *Memory) Dir(path string) billy.Filesystem { |
| return &Memory{ |
| base: fs.Join(fs.base, path), |
| s: fs.s, |
| } |
| } |
| |
| // Base returns the base path for the filesystem. |
| func (fs *Memory) Base() string { |
| return fs.base |
| } |
| |
| type file struct { |
| billy.BaseFile |
| |
| content *content |
| position int64 |
| flag int |
| mode os.FileMode |
| } |
| |
| func (f *file) Read(b []byte) (int, error) { |
| n, err := f.ReadAt(b, f.position) |
| if err != nil { |
| return 0, err |
| } |
| |
| return n, err |
| } |
| |
| func (f *file) ReadAt(b []byte, off int64) (int, error) { |
| if f.IsClosed() { |
| return 0, billy.ErrClosed |
| } |
| |
| if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) { |
| return 0, errors.New("read not supported") |
| } |
| |
| n, err := f.content.ReadAt(b, off) |
| f.position += int64(n) |
| |
| return n, err |
| } |
| |
| func (f *file) Seek(offset int64, whence int) (int64, error) { |
| if f.IsClosed() { |
| return 0, billy.ErrClosed |
| } |
| |
| switch whence { |
| case io.SeekCurrent: |
| f.position += offset |
| case io.SeekStart: |
| f.position = offset |
| case io.SeekEnd: |
| f.position = int64(f.content.Len()) + offset |
| } |
| |
| return f.position, nil |
| } |
| |
| func (f *file) Write(p []byte) (int, error) { |
| if f.IsClosed() { |
| return 0, billy.ErrClosed |
| } |
| |
| if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) { |
| return 0, errors.New("write not supported") |
| } |
| |
| n, err := f.content.WriteAt(p, f.position) |
| f.position += int64(n) |
| |
| return n, err |
| } |
| |
| func (f *file) Close() error { |
| if f.IsClosed() { |
| return errors.New("file already closed") |
| } |
| |
| f.Closed = true |
| return nil |
| } |
| |
| func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File { |
| new := &file{ |
| BaseFile: billy.BaseFile{BaseFilename: filename}, |
| content: f.content, |
| mode: mode, |
| flag: flag, |
| } |
| |
| if isAppend(flag) { |
| new.position = int64(new.content.Len()) |
| } |
| |
| if isTruncate(flag) { |
| new.content.Truncate() |
| } |
| |
| return new |
| } |
| |
| func (f *file) Stat() billy.FileInfo { |
| return &fileInfo{ |
| name: f.Filename(), |
| mode: f.mode, |
| size: f.content.Len(), |
| } |
| } |
| |
| type fileInfo struct { |
| name string |
| size int |
| mode os.FileMode |
| } |
| |
| func (fi *fileInfo) Name() string { |
| return fi.name |
| } |
| |
| func (fi *fileInfo) Size() int64 { |
| return int64(fi.size) |
| } |
| |
| func (fi *fileInfo) Mode() os.FileMode { |
| return fi.mode |
| } |
| |
| func (*fileInfo) ModTime() time.Time { |
| return time.Now() |
| } |
| |
| func (fi *fileInfo) IsDir() bool { |
| return fi.mode.IsDir() |
| } |
| |
| func (*fileInfo) Sys() interface{} { |
| return nil |
| } |
| |
| func (c *content) Truncate() { |
| c.bytes = make([]byte, 0) |
| } |
| |
| func (c *content) Len() int { |
| return len(c.bytes) |
| } |
| |
| func isCreate(flag int) bool { |
| return flag&os.O_CREATE != 0 |
| } |
| |
| func isAppend(flag int) bool { |
| return flag&os.O_APPEND != 0 |
| } |
| |
| func isTruncate(flag int) bool { |
| return flag&os.O_TRUNC != 0 |
| } |
| |
| func isReadAndWrite(flag int) bool { |
| return flag&os.O_RDWR != 0 |
| } |
| |
| func isReadOnly(flag int) bool { |
| return flag == os.O_RDONLY |
| } |
| |
| func isWriteOnly(flag int) bool { |
| return flag&os.O_WRONLY != 0 |
| } |