parent
57470b0615
commit
5f001da59e
14
README.md
14
README.md
|
@ -1,7 +1,7 @@
|
||||||
# XTFS
|
# Overlay
|
||||||
eXTended File System
|
Overlay File System
|
||||||
|
|
||||||
XTFS is an easy way to implement a file system in such a way that
|
Overlay is an easy way to implement a file system in such a way that
|
||||||
production assets can be overridden by assets on disk.
|
production assets can be overridden by assets on disk.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -12,14 +12,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
|
||||||
"go.jolheiser.com/xtfs"
|
"go.jolheiser.com/overlay"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed assets
|
//go:embed assets
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
xfs, err := xtfs.New("/var/lib/myapp/custom", assets)
|
ofs, err := overlay.New("/var/lib/myapp/custom", assets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -38,14 +38,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
|
||||||
"go.jolheiser.com/xtfs"
|
"go.jolheiser.com/overlay"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed assets
|
//go:embed assets
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
xfs, err := xtfs.New("/var/lib/myapp/custom", assets, xtfs.WithSub("assets"))
|
ofs, err := overlay.New("/var/lib/myapp/custom", assets, overlay.WithSub("assets"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
go test -benchmem -bench=.
|
go test -benchmem -bench=.
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
pkg: go.jolheiser.com/xtfs
|
pkg: go.jolheiser.com/overlay
|
||||||
cpu: Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz
|
cpu: Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz
|
||||||
BenchmarkCache-8 133917188 8.970 ns/op 0 B/op 0 allocs/op
|
BenchmarkCache-8 134959974 9.003 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkNoCache-8 931821 1362 ns/op 280 B/op 4 allocs/op
|
BenchmarkNoCache-8 897212 1369 ns/op 280 B/op 4 allocs/op
|
||||||
PASS
|
PASS
|
||||||
ok go.jolheiser.com/xtfs 3.394s
|
ok go.jolheiser.com/overlay 3.360s
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,3 @@
|
||||||
module go.jolheiser.com/xtfs
|
module go.jolheiser.com/overlay
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package xtfs
|
package overlay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
@ -15,7 +15,7 @@ func TestMain(m *testing.M) {
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestXTFS(t *testing.T) {
|
func TestOverlay(t *testing.T) {
|
||||||
tt := []struct {
|
tt := []struct {
|
||||||
Name string
|
Name string
|
||||||
File string
|
File string
|
||||||
|
@ -55,7 +55,7 @@ func TestXTFS(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.EqualFold(string(contents), tc.Expected) {
|
if !strings.EqualFold(string(contents), tc.Expected) {
|
||||||
t.Logf("xtfs did not match:\n\tgot: %s\n\texpected: %s\n", string(contents), tc.Expected)
|
t.Logf("fs did not match:\n\tgot: %s\n\texpected: %s\n", string(contents), tc.Expected)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -67,7 +67,7 @@ var emptyFS embed.FS
|
||||||
func TestInvalid(t *testing.T) {
|
func TestInvalid(t *testing.T) {
|
||||||
_, err := New("/var/lib/myapp/assets/custom", emptyFS)
|
_, err := New("/var/lib/myapp/assets/custom", emptyFS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log("invalid XTFS should not error explicitly")
|
t.Log("invalid FS should not error explicitly")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
79
xtfs.go
79
xtfs.go
|
@ -1,79 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue