Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add SetModified #17

Merged
merged 1 commit into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 80 additions & 66 deletions fs.go
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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