diff --git a/README.md b/README.md index 2f32c46f2b5a669669b08efc6e6a4d84fbcad2a6..e4486d8b8308677ad726201e119bc40d7715b878 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +# XTFS + # Overlay -Overlay File System + -Overlay is an easy way to implement a file system in such a way that +Overlay File System production assets can be overridden by assets on disk. ## Usage @@ -12,14 +14,14 @@ import ( "embed" - "go.jolheiser.com/overlay" + "go.jolheiser.com/xtfs" ) //go:embed assets var assets embed.FS func main() { - ofs, err := overlay.New("/var/lib/myapp/custom", assets) + xfs, err := xtfs.New("/var/lib/myapp/custom", assets) if err != nil { panic(err) } @@ -38,14 +40,14 @@ import ( "embed" - "go.jolheiser.com/overlay" + "go.jolheiser.com/xtfs" ) //go:embed assets var assets embed.FS func main() { - ofs, err := overlay.New("/var/lib/myapp/custom", assets, overlay.WithSub("assets")) + xfs, err := xtfs.New("/var/lib/myapp/custom", assets, xtfs.WithSub("assets")) if err != nil { panic(err) } diff --git a/bench.txt b/bench.txt index ba8bf45cbfa8cbd3d7b828835e06ca87a31b1fac..719cc74bfb761a6ae130e157433961678f621910 100644 --- a/bench.txt +++ b/bench.txt @@ -1,9 +1,9 @@ go test -benchmem -bench=. goos: linux goarch: amd64 -pkg: go.jolheiser.com/overlay +pkg: go.jolheiser.com/xtfs cpu: Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz -BenchmarkCache-8 134959974 9.003 ns/op 0 B/op 0 allocs/op +BenchmarkCache-8 133917188 8.970 ns/op 0 B/op 0 allocs/op -BenchmarkNoCache-8 897212 1369 ns/op 280 B/op 4 allocs/op +BenchmarkNoCache-8 931821 1362 ns/op 280 B/op 4 allocs/op PASS -ok go.jolheiser.com/overlay 3.360s +ok go.jolheiser.com/xtfs 3.394s diff --git a/go.mod b/go.mod index 82d8e4b497dda07afb4c40008ff664431d1e4a18..158d9ab2b80f36645fb38c03ef4e00dcc42ff541 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module go.jolheiser.com/overlay +module go.jolheiser.com/xtfs go 1.16 diff --git a/overlay.go b/overlay.go deleted file mode 100644 index 84410a2a79651f909e1094f3d032d758622e37f0..0000000000000000000000000000000000000000 --- a/overlay.go +++ /dev/null @@ -1,79 +0,0 @@ -package overlay - -import ( - "io/fs" - "os" - "path" -) - -// FS is an overlay File System -type FS struct { - fs fs.FS - root string - doCache bool - cache map[string]bool -} - -func (f *FS) apn(name string) string { - return path.Join(f.root, name) -} - -func (f *FS) exists(name string) bool { - if has, ok := f.cache[name]; ok && f.doCache { - return has - } - _, err := os.Stat(f.apn(name)) - if err != nil { - f.cache[name] = false - return false - } - f.cache[name] = true - return true -} - -// Open opens an fs.File, preferring disk -func (f *FS) Open(name string) (fs.File, error) { - if f.exists(name) { - return os.Open(f.apn(name)) - } - return f.fs.Open(name) -} - -// Option is a functional option for an FS -type Option func(*FS) error - -// New returns a new FS -func New(root string, fs fs.FS, opts ...Option) (*FS, error) { - x := &FS{ - fs: fs, - root: root, - doCache: true, - cache: make(map[string]bool), - } - - for _, opt := range opts { - if err := opt(x); err != nil { - return x, err - } - } - - return x, nil -} - -// WithSub sets a fs.Sub for an FS -func WithSub(sub string) Option { - return func(x *FS) (err error) { - x.fs, err = fs.Sub(x.fs, sub) - return - } -} - -// WithCaching sets a caching mode for an FS -// Caching avoids subsequent os.Stat to determine if a file exists on disk -// See bench.txt for differences in usage -func WithCaching(doCache bool) Option { - return func(x *FS) error { - x.doCache = doCache - return nil - } -} diff --git a/overlay_test.go b/xtfs_test.go rename from overlay_test.go rename to xtfs_test.go index 2992095d9383a209fd4a734b9914be20dc51c00a..c89f7dc86ed62688f6617715b5f8a0f350aac817 100644 --- a/overlay_test.go +++ b/xtfs_test.go @@ -1,4 +1,4 @@ -package overlay +package xtfs import ( "embed" @@ -15,8 +15,8 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -package overlay "io" +) tt := []struct { Name string File string @@ -56,7 +56,7 @@ t.FailNow() } if !strings.EqualFold(string(contents), tc.Expected) { - t.Logf("fs did not match:\n\tgot: %s\n\texpected: %s\n", string(contents), tc.Expected) + t.Logf("xtfs did not match:\n\tgot: %s\n\texpected: %s\n", string(contents), tc.Expected) t.FailNow() } }) @@ -68,7 +68,7 @@ func TestInvalid(t *testing.T) { _, err := New("/var/lib/myapp/assets/custom", emptyFS) if err != nil { - t.Log("invalid FS should not error explicitly") + t.Log("invalid XTFS should not error explicitly") t.FailNow() } } diff --git a/xtfs.go b/xtfs.go new file mode 100644 index 0000000000000000000000000000000000000000..eaa03cdf7823070ad7e94666615ff04d32c8ded9 --- /dev/null +++ b/xtfs.go @@ -0,0 +1,79 @@ +package xtfs + +import ( + "io/fs" + "os" + "path" +) + +// XTFS is an eXTended File System +type XTFS struct { + fs fs.FS + root string + doCache bool + cache map[string]bool +} + +func (x *XTFS) apn(name string) string { + return path.Join(x.root, name) +} + +func (x *XTFS) exists(name string) bool { + if has, ok := x.cache[name]; ok && x.doCache { + return has + } + _, err := os.Stat(x.apn(name)) + if err != nil { + x.cache[name] = false + return false + } + x.cache[name] = true + return true +} + +// Open opens an fs.File, preferring disk +func (x *XTFS) Open(name string) (fs.File, error) { + if x.exists(name) { + return os.Open(x.apn(name)) + } + return x.fs.Open(name) +} + +// Option is a functional option for an XTFS +type Option func(*XTFS) error + +// New returns a new XTFS +func New(root string, fs fs.FS, opts ...Option) (*XTFS, error) { + x := &XTFS{ + fs: fs, + root: root, + doCache: true, + cache: make(map[string]bool), + } + + for _, opt := range opts { + if err := opt(x); err != nil { + return x, err + } + } + + return x, nil +} + +// WithSub sets a fs.Sub for an XTFS +func WithSub(sub string) Option { + return func(x *XTFS) (err error) { + x.fs, err = fs.Sub(x.fs, sub) + return + } +} + +// WithCaching sets a caching mode for an XTFS +// Caching avoids subsequent os.Stat to determine if a file exists on disk +// See bench.txt for differences in usage +func WithCaching(doCache bool) Option { + return func(x *XTFS) error { + x.doCache = doCache + return nil + } +}