146 lines
3.3 KiB
Go
146 lines
3.3 KiB
Go
package vfs
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type ResolveFS struct {
|
|
osDir string
|
|
osFS *OsFS
|
|
resolver *resolver
|
|
}
|
|
|
|
func NewResolveFS(osDir string, factories map[string]FsFactory) *ResolveFS {
|
|
return &ResolveFS{
|
|
osDir: osDir,
|
|
osFS: NewOsFs(osDir),
|
|
resolver: newResolver(factories),
|
|
}
|
|
}
|
|
|
|
// Open implements Filesystem.
|
|
func (r *ResolveFS) Open(filename string) (File, error) {
|
|
fsPath, nestedFs, nestedFsPath, err := r.resolver.resolvePath(filename, r.osFS.Open)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if nestedFs != nil {
|
|
return nestedFs.Open(nestedFsPath)
|
|
}
|
|
|
|
return r.osFS.Open(fsPath)
|
|
}
|
|
|
|
// ReadDir implements Filesystem.
|
|
func (r *ResolveFS) ReadDir(dir string) (map[string]File, error) {
|
|
fsPath, nestedFs, nestedFsPath, err := r.resolver.resolvePath(dir, r.osFS.Open)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if nestedFs != nil {
|
|
return nestedFs.ReadDir(nestedFsPath)
|
|
}
|
|
|
|
return r.osFS.ReadDir(fsPath)
|
|
}
|
|
|
|
var _ Filesystem = &ResolveFS{}
|
|
|
|
type FsFactory func(f File) (Filesystem, error)
|
|
|
|
const Separator = "/"
|
|
|
|
func newResolver(factories map[string]FsFactory) *resolver {
|
|
return &resolver{
|
|
factories: factories,
|
|
fsmap: map[string]Filesystem{},
|
|
}
|
|
}
|
|
|
|
type resolver struct {
|
|
m sync.Mutex
|
|
factories map[string]FsFactory
|
|
fsmap map[string]Filesystem // filesystem cache
|
|
// TODO: add fsmap clean
|
|
}
|
|
|
|
type openFile func(path string) (File, error)
|
|
|
|
// open requeue raw open, without resolver call
|
|
func (r *resolver) resolvePath(name string, rawOpen openFile) (fsPath string, nestedFs Filesystem, nestedFsPath string, err error) {
|
|
name = strings.TrimPrefix(name, Separator)
|
|
parts := strings.Split(name, Separator)
|
|
|
|
nestOn := -1
|
|
var nestFactory FsFactory
|
|
|
|
PARTS_LOOP:
|
|
for i, part := range parts {
|
|
for ext, factory := range r.factories {
|
|
if strings.HasSuffix(part, ext) {
|
|
nestOn = i + 1
|
|
nestFactory = factory
|
|
break PARTS_LOOP
|
|
}
|
|
}
|
|
}
|
|
|
|
if nestOn == -1 {
|
|
return name, nil, "", nil
|
|
}
|
|
|
|
fsPath = Clean(strings.Join(parts[:nestOn], Separator))
|
|
nestedFsPath = Clean(strings.Join(parts[nestOn:], Separator))
|
|
|
|
// we dont need lock until now
|
|
// it must be before fsmap read to exclude race condition:
|
|
// read -> write
|
|
// read -> write
|
|
r.m.Lock()
|
|
defer r.m.Unlock()
|
|
|
|
if nestedFs, ok := r.fsmap[fsPath]; ok {
|
|
return fsPath, nestedFs, nestedFsPath, nil
|
|
} else {
|
|
fsFile, err := rawOpen(fsPath)
|
|
if err != nil {
|
|
return "", nil, "", fmt.Errorf("error opening filesystem file: %s with error: %w", fsPath, err)
|
|
}
|
|
nestedFs, err := nestFactory(fsFile)
|
|
if err != nil {
|
|
return "", nil, "", fmt.Errorf("error creating filesystem from file: %s with error: %w", fsPath, err)
|
|
}
|
|
r.fsmap[fsPath] = nestedFs
|
|
|
|
return fsPath, nestedFs, nestedFsPath, nil
|
|
}
|
|
|
|
}
|
|
|
|
// func (r *resolver) resolveFile(name string, fs Filesystem) (File, error) {
|
|
// fsPath, nestedFs, nestedFsPath, err := r.resolvePath(name, fs)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
|
|
// if nestedFs == nil {
|
|
// return fs.Open(fsPath)
|
|
// }
|
|
|
|
// return nestedFs.Open(nestedFsPath)
|
|
// }
|
|
|
|
// func (r *resolver) resolveDir(name string, fs Filesystem) (map[string]File, error) {
|
|
// fsPath, nestedFs, nestedFsPath, err := r.resolvePath(name, fs)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
|
|
// if nestedFs == nil {
|
|
// return fs.ReadDir(fsPath)
|
|
// }
|
|
|
|
// return nestedFs.ReadDir(nestedFsPath)
|
|
// }
|