Skip to content

Commit

Permalink
feat: Add SetModified (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
yktakaha4 committed Nov 7, 2022
1 parent d2bd731 commit b382a6f
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 66 deletions.
146 changes: 80 additions & 66 deletions fs.go
@@ -1,82 +1,82 @@
package memoryfs

import (
"io"
"io/fs"
"io/ioutil"
"strings"
"time"
"io"
"io/fs"
"io/ioutil"
"strings"
"time"
)

// FS is an in-memory filesystem
type FS struct {
dir *dir
dir *dir
}

// New creates a new filesystem
func New() *FS {
return &FS{
dir: &dir{
info: fileinfo{
name: ".",
size: 0x100,
modified: time.Now(),
isDir: true,
mode: 0o700,
},
dirs: map[string]*dir{},
files: map[string]*file{},
},
}
return &FS{
dir: &dir{
info: fileinfo{
name: ".",
size: 0x100,
modified: time.Now(),
isDir: true,
mode: 0o700,
},
dirs: map[string]*dir{},
files: map[string]*file{},
},
}
}

// CloneFS allows you to take on fs.FS and wrap it in an fs that is writable
func CloneFS(base fs.FS) *FS {
newFS := New()
fs.WalkDir(base, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if d.IsDir() {
return newFS.MkdirAll(path, d.Type().Perm())
}

// Lazy write the files, holding onto the base FS to read the content on demand
return newFS.WriteLazyFile(path, func() (io.Reader, error) {
return base.Open(path)
}, d.Type().Perm())
})

return newFS
newFS := New()
fs.WalkDir(base, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if d.IsDir() {
return newFS.MkdirAll(path, d.Type().Perm())
}

// Lazy write the files, holding onto the base FS to read the content on demand
return newFS.WriteLazyFile(path, func() (io.Reader, error) {
return base.Open(path)
}, d.Type().Perm())
})

return newFS
}

// Stat returns a FileInfo describing the file.
func (m *FS) Stat(name string) (fs.FileInfo, error) {
name = cleanse(name)
if f, err := m.dir.getFile(name); err == nil {
return f.stat(), nil
}
if f, err := m.dir.getDir(name); err == nil {
return f.Stat()
}
return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrNotExist}
name = cleanse(name)
if f, err := m.dir.getFile(name); err == nil {
return f.stat(), nil
}
if f, err := m.dir.getDir(name); err == nil {
return f.Stat()
}
return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrNotExist}
}

// ReadDir reads the named directory
// and returns a list of directory entries sorted by filename.
func (m *FS) ReadDir(name string) ([]fs.DirEntry, error) {
return m.dir.ReadDir(cleanse(name))
return m.dir.ReadDir(cleanse(name))
}

// Open opens the named file for reading.
func (m *FS) Open(name string) (fs.File, error) {
return m.dir.Open(cleanse(name))
return m.dir.Open(cleanse(name))
}

// WriteFile writes the specified bytes to the named file. If the file exists, it will be overwritten.
func (m *FS) WriteFile(path string, data []byte, perm fs.FileMode) error {
return m.dir.WriteFile(cleanse(path), data, perm)
return m.dir.WriteFile(cleanse(path), data, perm)
}

// MkdirAll creates a directory named path,
Expand All @@ -87,7 +87,7 @@ func (m *FS) WriteFile(path string, data []byte, perm fs.FileMode) error {
// If path is already a directory, MkdirAll does nothing
// and returns nil.
func (m *FS) MkdirAll(path string, perm fs.FileMode) error {
return m.dir.MkdirAll(cleanse(path), perm)
return m.dir.MkdirAll(cleanse(path), perm)
}

// ReadFile reads the named file and returns its contents.
Expand All @@ -98,23 +98,23 @@ func (m *FS) MkdirAll(path string, perm fs.FileMode) error {
// The caller is permitted to modify the returned byte slice.
// This method should return a copy of the underlying data.
func (m *FS) ReadFile(name string) ([]byte, error) {
f, err := m.dir.Open(cleanse(name))
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
return ioutil.ReadAll(f)
f, err := m.dir.Open(cleanse(name))
if err != nil {
return nil, err
}
defer func() { _ = f.Close() }()
return ioutil.ReadAll(f)
}

// Sub returns an FS corresponding to the subtree rooted at dir.
func (m *FS) Sub(dir string) (fs.FS, error) {
d, err := m.dir.getDir(cleanse(dir))
if err != nil {
return nil, err
}
return &FS{
dir: d,
}, nil
d, err := m.dir.getDir(cleanse(dir))
if err != nil {
return nil, err
}
return &FS{
dir: d,
}, nil
}

// Glob returns the names of all files matching pattern or nil
Expand All @@ -126,22 +126,36 @@ func (m *FS) Sub(dir string) (fs.FS, error) {
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
func (m *FS) Glob(pattern string) ([]string, error) {
pattern = strings.ReplaceAll(pattern, "/", separator)
return m.dir.glob(pattern)
pattern = strings.ReplaceAll(pattern, "/", separator)
return m.dir.glob(pattern)
}

// WriteLazyFile creates (or overwrites) the named file.
// The contents of the file are not set at this time, but are read on-demand later using the provided LazyOpener.
func (m *FS) WriteLazyFile(path string, opener LazyOpener, perm fs.FileMode) error {
return m.dir.WriteLazyFile(cleanse(path), opener, perm)
return m.dir.WriteLazyFile(cleanse(path), opener, perm)
}

// Remove deletes a file or directory from the filesystem
func (m *FS) Remove(path string) error {
return m.dir.Remove(cleanse(path))
return m.dir.Remove(cleanse(path))
}

// RemoveAll deletes a file or directory and any children if present from the filesystem
func (m *FS) RemoveAll(path string) error {
return m.dir.RemoveAll(cleanse(path))
return m.dir.RemoveAll(cleanse(path))
}

// SetModified set modified time to file or directory
func (m *FS) SetModified(name string, modified time.Time) error {
name = cleanse(name)
if f, err := m.dir.getFile(name); err == nil {
f.info.modified = modified
return nil
}
if f, err := m.dir.getDir(name); err == nil {
f.info.modified = modified
return nil
}
return &fs.PathError{Op: "set modified", Path: name, Err: fs.ErrNotExist}
}
26 changes: 26 additions & 0 deletions fs_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"sync"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -218,6 +219,31 @@ func Test_AllOperations(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, []byte{4, 5, 6}, data)
})

t.Run("Set modified time to directory", func(t *testing.T) {
modified := time.Date(2112, 9, 3, 12, 34, 56, 321, time.UTC)

err := memfs.SetModified("files/a/b/c", modified)
assert.NoError(t, err)

stat, err := memfs.Stat("files/a/b/c")
assert.NoError(t, err)
assert.Equal(t, modified, stat.ModTime())
})

t.Run("Set modified time to file", func(t *testing.T) {
modified := time.Date(2112, 9, 3, 12, 34, 56, 321, time.UTC)

err := memfs.SetModified("test.txt", modified)
assert.NoError(t, err)

stat, err := memfs.Stat("test.txt")
assert.NoError(t, err)
assert.Equal(t, modified, stat.ModTime())

_, err = memfs.Stat("not_found.txt")
assert.Error(t, err)
})
}

func Test_ConcurrentWritesToDirectory(t *testing.T) {
Expand Down

0 comments on commit b382a6f

Please sign in to comment.