blob: 3f10d139a705a42fe4e77e18fcb432b034b3fdbc [file]
// 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
}