commit f146f95efcc01b7aa6c8082f1b898b6f6a1890db Author: Miroslav Misek Date: Thu Mar 27 08:58:59 2025 +0100 Initial commit diff --git a/.idea/mergefs.iml b/.idea/mergefs.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/mergefs.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b334d7e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..0e0384f --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1ca2857 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module netgarden.dev/netgarden/mergefs + +go 1.24.1 diff --git a/mergefs.go b/mergefs.go new file mode 100644 index 0000000..2197792 --- /dev/null +++ b/mergefs.go @@ -0,0 +1,66 @@ +package mergefs + +import ( + "errors" + "io/fs" + "os" +) + +// Merge merges the given filesystems together, +func Merge(filesystems ...fs.FS) fs.FS { + return MergedFS{filesystems: filesystems} +} + +// MergedFS combines filesystems. Each filesystem can serve different paths. +// The first FS takes precedence +type MergedFS struct { + filesystems []fs.FS +} + +// Open opens the named file. +func (mfs MergedFS) Open(name string) (fs.File, error) { + for _, fs := range mfs.filesystems { + file, err := fs.Open(name) + if err == nil { // TODO should we return early when it's not an os.ErrNotExist? Should we offer options to decide this behaviour? + return file, nil + } + } + return nil, os.ErrNotExist +} + +// ReadDir reads from the directory, and produces a DirEntry array of different +// directories. +// +// It iterates through all different filesystems that exist in the mfs MergeFS +// filesystem slice and it identifies overlapping directories that exist in different +// filesystems +func (mfs MergedFS) ReadDir(name string) ([]fs.DirEntry, error) { + dirsMap := make(map[string]fs.DirEntry) + notExistCount := 0 + for _, filesystem := range mfs.filesystems { + dir, err := fs.ReadDir(filesystem, name) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + notExistCount++ + continue + } + return nil, err + } + for _, v := range dir { + if _, ok := dirsMap[v.Name()]; !ok { + dirsMap[v.Name()] = v + } + } + continue + } + if len(mfs.filesystems) == notExistCount { + return nil, fs.ErrNotExist + } + dirs := make([]fs.DirEntry, 0, len(dirsMap)) + + for _, value := range dirsMap { + dirs = append(dirs, value) + } + + return dirs, nil +}