From b382a6fb030d7c6b56655596105177a2c2c18ebf Mon Sep 17 00:00:00 2001 From: Yuuki Takahashi <20282867+yktakaha4@users.noreply.github.com> Date: Mon, 7 Nov 2022 20:05:07 +0900 Subject: [PATCH] feat: Add SetModified (#17) --- fs.go | 146 +++++++++++++++++++++++++++++------------------------ fs_test.go | 26 ++++++++++ 2 files changed, 106 insertions(+), 66 deletions(-) diff --git a/fs.go b/fs.go index dd52574..876025f 100644 --- a/fs.go +++ b/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, @@ -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. @@ -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 @@ -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} } diff --git a/fs_test.go b/fs_test.go index a8224e4..79940b6 100644 --- a/fs_test.go +++ b/fs_test.go @@ -9,6 +9,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -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) {