datarhei-dragonfork-core/io/fs/mem.go

734 lines
12 KiB
Go
Raw Normal View History

2022-05-13 13:26:45 -04:00
package fs
import (
"bytes"
"fmt"
"io"
2023-02-01 10:09:20 -05:00
"io/fs"
"os"
"path/filepath"
2022-05-13 13:26:45 -04:00
"sort"
2023-02-01 10:09:20 -05:00
"strings"
2022-05-13 13:26:45 -04:00
"sync"
"time"
"github.com/datarhei/core/v16/glob"
"github.com/datarhei/core/v16/log"
2022-05-13 13:26:45 -04:00
)
// MemConfig is the config that is required for creating
// a new memory filesystem.
type MemConfig struct {
2023-02-01 10:09:20 -05:00
Logger log.Logger // For logging, optional
2022-05-13 13:26:45 -04:00
}
type memFileInfo struct {
2023-02-01 10:09:20 -05:00
name string // Full name of the file (including path)
size int64 // The size of the file in bytes
dir bool // Whether this file represents a directory
lastMod time.Time // The time of the last modification of the file
linkTo string // Where the file links to, empty if it's not a link
2022-05-13 13:26:45 -04:00
}
func (f *memFileInfo) Name() string {
return f.name
}
func (f *memFileInfo) Size() int64 {
return f.size
}
2023-02-01 10:09:20 -05:00
func (f *memFileInfo) Mode() fs.FileMode {
mode := fs.FileMode(fs.ModePerm)
if f.dir {
mode |= fs.ModeDir
}
if len(f.linkTo) != 0 {
mode |= fs.ModeSymlink
}
return mode
}
2022-05-13 13:26:45 -04:00
func (f *memFileInfo) ModTime() time.Time {
return f.lastMod
}
func (f *memFileInfo) IsLink() (string, bool) {
return f.linkTo, len(f.linkTo) != 0
}
func (f *memFileInfo) IsDir() bool {
2023-02-01 10:09:20 -05:00
return f.dir
2022-05-13 13:26:45 -04:00
}
type memFile struct {
2023-02-01 10:09:20 -05:00
memFileInfo
data *bytes.Buffer // Contents of the file
2022-05-13 13:26:45 -04:00
}
func (f *memFile) Name() string {
return f.name
}
func (f *memFile) Stat() (FileInfo, error) {
info := &memFileInfo{
name: f.name,
size: f.size,
2023-02-01 10:09:20 -05:00
dir: f.dir,
2022-05-13 13:26:45 -04:00
lastMod: f.lastMod,
linkTo: f.linkTo,
}
return info, nil
}
func (f *memFile) Read(p []byte) (int, error) {
if f.data == nil {
return 0, io.EOF
}
return f.data.Read(p)
}
func (f *memFile) Close() error {
if f.data == nil {
return io.EOF
}
f.data = nil
return nil
}
type memFilesystem struct {
2023-02-01 10:09:20 -05:00
metadata map[string]string
metaLock sync.RWMutex
2022-05-13 13:26:45 -04:00
// Mapping of path to file
files *memStorage
2022-05-13 13:26:45 -04:00
// Current size of the filesystem in bytes and its mutes
2022-05-13 13:26:45 -04:00
currentSize int64
sizeLock sync.RWMutex
2022-05-13 13:26:45 -04:00
// Logger from the config
logger log.Logger
}
// NewMemFilesystem creates a new filesystem in memory that implements
// the Filesystem interface.
2023-02-01 10:09:20 -05:00
func NewMemFilesystem(config MemConfig) (Filesystem, error) {
2022-05-13 13:26:45 -04:00
fs := &memFilesystem{
2023-02-01 10:09:20 -05:00
metadata: make(map[string]string),
logger: config.Logger,
2022-05-13 13:26:45 -04:00
}
if fs.logger == nil {
2023-01-31 08:45:58 -05:00
fs.logger = log.New("")
2022-05-13 13:26:45 -04:00
}
2023-01-31 08:45:58 -05:00
fs.logger = fs.logger.WithField("type", "mem")
fs.files = newMemStorage()
2022-05-13 13:26:45 -04:00
2023-02-01 10:09:20 -05:00
fs.logger.Debug().Log("Created")
2022-05-13 13:26:45 -04:00
2023-02-01 10:09:20 -05:00
return fs, nil
}
func NewMemFilesystemFromDir(dir string, config MemConfig) (Filesystem, error) {
mem, err := NewMemFilesystem(config)
if err != nil {
return nil, err
}
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() {
return nil
}
mode := info.Mode()
if !mode.IsRegular() {
return nil
}
if mode&os.ModeSymlink != 0 {
return nil
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer file.Close()
_, _, err = mem.WriteFileReader(path, file)
if err != nil {
return fmt.Errorf("can't copy %s", path)
}
return nil
})
if err != nil {
return nil, err
}
return mem, nil
2022-05-13 13:26:45 -04:00
}
2023-01-31 08:45:58 -05:00
func (fs *memFilesystem) Name() string {
2023-02-01 10:09:20 -05:00
return "mem"
2023-01-31 08:45:58 -05:00
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) Type() string {
return "mem"
2022-05-13 13:26:45 -04:00
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) Metadata(key string) string {
fs.metaLock.RLock()
defer fs.metaLock.RUnlock()
2022-05-13 13:26:45 -04:00
2023-02-01 10:09:20 -05:00
return fs.metadata[key]
2022-05-13 13:26:45 -04:00
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) SetMetadata(key, data string) {
fs.metaLock.Lock()
defer fs.metaLock.Unlock()
fs.metadata[key] = data
2023-01-31 08:45:58 -05:00
}
2022-05-13 13:26:45 -04:00
func (fs *memFilesystem) Size() (int64, int64) {
fs.sizeLock.RLock()
defer fs.sizeLock.RUnlock()
2022-05-13 13:26:45 -04:00
2023-02-01 10:09:20 -05:00
return fs.currentSize, -1
2022-05-13 13:26:45 -04:00
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) Files() int64 {
nfiles := int64(0)
2022-05-13 13:26:45 -04:00
fs.files.Range(func(key string, f *memFile) bool {
2023-02-01 10:09:20 -05:00
if f.dir {
return true
2023-02-01 10:09:20 -05:00
}
2022-05-13 13:26:45 -04:00
2023-02-01 10:09:20 -05:00
nfiles++
return true
})
2022-05-13 13:26:45 -04:00
2023-02-01 10:09:20 -05:00
return nfiles
2022-05-13 13:26:45 -04:00
}
func (fs *memFilesystem) Open(path string) File {
2023-02-01 10:09:20 -05:00
path = fs.cleanPath(path)
file, ok := fs.files.LoadAndCopy(path)
2022-05-13 13:26:45 -04:00
if !ok {
return nil
}
newFile := &memFile{
2023-02-01 10:09:20 -05:00
memFileInfo: memFileInfo{
name: file.name,
size: file.size,
lastMod: file.lastMod,
linkTo: file.linkTo,
},
}
if len(file.linkTo) != 0 {
file.Close()
file, ok = fs.files.LoadAndCopy(file.linkTo)
2023-02-01 10:09:20 -05:00
if !ok {
return nil
}
2022-05-13 13:26:45 -04:00
}
if file.data != nil {
2023-02-01 10:09:20 -05:00
newFile.lastMod = file.lastMod
2022-05-13 13:26:45 -04:00
newFile.data = bytes.NewBuffer(file.data.Bytes())
2023-02-01 10:09:20 -05:00
newFile.size = int64(newFile.data.Len())
2022-05-13 13:26:45 -04:00
}
return newFile
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) ReadFile(path string) ([]byte, error) {
path = fs.cleanPath(path)
file, ok := fs.files.LoadAndCopy(path)
2023-02-01 10:09:20 -05:00
if !ok {
return nil, os.ErrNotExist
}
if len(file.linkTo) != 0 {
file.Close()
file, ok = fs.files.LoadAndCopy(file.linkTo)
2023-02-01 10:09:20 -05:00
if !ok {
return nil, os.ErrNotExist
}
}
defer file.Close()
2023-02-01 10:09:20 -05:00
if file.data != nil {
return file.data.Bytes(), nil
}
return nil, nil
}
2022-05-13 13:26:45 -04:00
func (fs *memFilesystem) Symlink(oldname, newname string) error {
2023-02-01 10:09:20 -05:00
oldname = fs.cleanPath(oldname)
newname = fs.cleanPath(newname)
if fs.files.Has(newname) {
return os.ErrExist
2022-05-13 13:26:45 -04:00
}
oldFile, ok := fs.files.Load(oldname)
if !ok {
return os.ErrNotExist
2022-05-13 13:26:45 -04:00
}
if len(oldFile.linkTo) != 0 {
return fmt.Errorf("%s can't link to another link (%s)", newname, oldname)
2022-05-13 13:26:45 -04:00
}
newFile := &memFile{
2023-02-01 10:09:20 -05:00
memFileInfo: memFileInfo{
name: newname,
dir: false,
size: 0,
lastMod: time.Now(),
linkTo: oldname,
},
data: nil,
2022-05-13 13:26:45 -04:00
}
oldFile, loaded := fs.files.Store(newname, newFile)
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
if loaded {
oldFile.Close()
fs.currentSize -= oldFile.size
}
fs.currentSize += newFile.size
2022-05-13 13:26:45 -04:00
return nil
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) WriteFileReader(path string, r io.Reader) (int64, bool, error) {
path = fs.cleanPath(path)
2022-05-13 13:26:45 -04:00
newFile := &memFile{
2023-02-01 10:09:20 -05:00
memFileInfo: memFileInfo{
name: path,
dir: false,
size: 0,
lastMod: time.Now(),
},
data: &bytes.Buffer{},
2022-05-13 13:26:45 -04:00
}
2023-02-01 10:09:20 -05:00
size, err := newFile.data.ReadFrom(r)
2022-05-13 13:26:45 -04:00
if err != nil {
fs.logger.WithFields(log.Fields{
"path": path,
"filesize_bytes": size,
"error": err,
}).Warn().Log("Incomplete file")
}
2023-02-01 10:09:20 -05:00
newFile.size = size
2022-05-13 13:26:45 -04:00
oldFile, replace := fs.files.Store(path, newFile)
2022-05-13 13:26:45 -04:00
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
2022-05-13 13:26:45 -04:00
if replace {
oldFile.Close()
2022-05-13 13:26:45 -04:00
fs.currentSize -= oldFile.size
2022-05-13 13:26:45 -04:00
}
2023-02-01 10:09:20 -05:00
fs.currentSize += newFile.size
2022-05-13 13:26:45 -04:00
logger := fs.logger.WithFields(log.Fields{
"path": newFile.name,
"filesize_bytes": newFile.size,
"size_bytes": fs.currentSize,
})
if replace {
logger.Debug().Log("Replaced file")
} else {
logger.Debug().Log("Added file")
}
return newFile.size, !replace, nil
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) WriteFile(path string, data []byte) (int64, bool, error) {
return fs.WriteFileReader(path, bytes.NewBuffer(data))
}
func (fs *memFilesystem) WriteFileSafe(path string, data []byte) (int64, bool, error) {
return fs.WriteFileReader(path, bytes.NewBuffer(data))
}
func (fs *memFilesystem) Purge(size int64) int64 {
2022-05-13 13:26:45 -04:00
files := []*memFile{}
fs.files.Range(func(_ string, file *memFile) bool {
files = append(files, file)
return true
})
2022-05-13 13:26:45 -04:00
sort.Slice(files, func(i, j int) bool {
return files[i].lastMod.Before(files[j].lastMod)
})
var freed int64 = 0
for _, f := range files {
fs.files.Delete(f.name)
2022-05-13 13:26:45 -04:00
size -= f.size
freed += f.size
fs.sizeLock.Lock()
2022-05-13 13:26:45 -04:00
fs.currentSize -= f.size
fs.sizeLock.Unlock()
2022-05-13 13:26:45 -04:00
f.Close()
2022-05-13 13:26:45 -04:00
fs.logger.WithFields(log.Fields{
"path": f.name,
"filesize_bytes": f.size,
"size_bytes": fs.currentSize,
}).Debug().Log("Purged file")
if size <= 0 {
break
}
}
files = nil
return freed
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) MkdirAll(path string, perm os.FileMode) error {
path = fs.cleanPath(path)
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
2023-02-01 10:09:20 -05:00
info, err := fs.stat(path)
if err == nil {
if info.IsDir() {
return nil
}
return os.ErrExist
}
f := &memFile{
memFileInfo: memFileInfo{
name: path,
size: 0,
dir: true,
lastMod: time.Now(),
},
data: nil,
}
fs.files.Store(path, f)
2023-02-01 10:09:20 -05:00
return nil
}
func (fs *memFilesystem) Rename(src, dst string) error {
src = filepath.Join("/", filepath.Clean(src))
dst = filepath.Join("/", filepath.Clean(dst))
if src == dst {
return nil
}
srcFile, ok := fs.files.Load(src)
2023-02-01 10:09:20 -05:00
if !ok {
return os.ErrNotExist
}
dstFile, replace := fs.files.Store(dst, srcFile)
fs.files.Delete(src)
2023-02-01 10:09:20 -05:00
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
2023-02-01 10:09:20 -05:00
if replace {
dstFile.Close()
fs.currentSize -= dstFile.size
}
2023-02-01 10:09:20 -05:00
return nil
}
func (fs *memFilesystem) Copy(src, dst string) error {
src = filepath.Join("/", filepath.Clean(src))
dst = filepath.Join("/", filepath.Clean(dst))
if src == dst {
return nil
}
if fs.isDir(dst) {
return os.ErrInvalid
}
2023-02-01 10:09:20 -05:00
srcFile, ok := fs.files.LoadAndCopy(src)
2023-02-01 10:09:20 -05:00
if !ok {
return os.ErrNotExist
}
if srcFile.dir {
srcFile.Close()
2023-02-01 10:09:20 -05:00
return os.ErrNotExist
}
dstFile := &memFile{
memFileInfo: memFileInfo{
name: dst,
dir: false,
size: srcFile.size,
lastMod: time.Now(),
},
data: srcFile.data,
2023-02-01 10:09:20 -05:00
}
f, replace := fs.files.Store(dst, dstFile)
2023-02-01 10:09:20 -05:00
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
2023-02-01 10:09:20 -05:00
if replace {
f.Close()
fs.currentSize -= f.size
}
2023-02-01 10:09:20 -05:00
fs.currentSize += dstFile.size
2023-02-01 10:09:20 -05:00
return nil
}
func (fs *memFilesystem) Stat(path string) (FileInfo, error) {
path = fs.cleanPath(path)
return fs.stat(path)
}
func (fs *memFilesystem) stat(path string) (FileInfo, error) {
file, ok := fs.files.Load(path)
2023-02-01 10:09:20 -05:00
if ok {
f := &memFileInfo{
name: file.name,
size: file.size,
dir: file.dir,
lastMod: file.lastMod,
linkTo: file.linkTo,
}
if len(f.linkTo) != 0 {
file, ok := fs.files.Load(f.linkTo)
2023-02-01 10:09:20 -05:00
if !ok {
return nil, os.ErrNotExist
}
f.lastMod = file.lastMod
f.size = file.size
}
return f, nil
}
// Check for directories
if !fs.isDir(path) {
return nil, os.ErrNotExist
}
f := &memFileInfo{
name: path,
size: 0,
dir: true,
lastMod: time.Now(),
linkTo: "",
}
return f, nil
}
func (fs *memFilesystem) isDir(path string) bool {
file, ok := fs.files.Load(path)
2023-02-01 10:09:20 -05:00
if ok {
return file.dir
}
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
if path == "/" {
return true
}
found := false
fs.files.Range(func(k string, _ *memFile) bool {
2023-02-01 10:09:20 -05:00
if strings.HasPrefix(k, path) {
found = true
return false
2023-02-01 10:09:20 -05:00
}
return true
})
return found
2023-02-01 10:09:20 -05:00
}
func (fs *memFilesystem) Remove(path string) int64 {
file, ok := fs.files.Delete(path)
if ok {
file.Close()
2023-02-01 10:09:20 -05:00
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
2022-05-13 13:26:45 -04:00
fs.currentSize -= file.size
} else {
return -1
}
fs.logger.WithFields(log.Fields{
"path": file.name,
"filesize_bytes": file.size,
"size_bytes": fs.currentSize,
}).Debug().Log("Removed file")
return file.size
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) RemoveAll() int64 {
fs.sizeLock.Lock()
defer fs.sizeLock.Unlock()
2022-05-13 13:26:45 -04:00
size := fs.currentSize
fs.files = newMemStorage()
2022-05-13 13:26:45 -04:00
fs.currentSize = 0
return size
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) List(path, pattern string) []FileInfo {
path = fs.cleanPath(path)
2022-05-13 13:26:45 -04:00
files := []FileInfo{}
var compiledPattern glob.Glob
var err error
2022-05-13 13:26:45 -04:00
if len(pattern) != 0 {
compiledPattern, err = glob.Compile(pattern, '/')
if err != nil {
return nil
2023-02-01 10:09:20 -05:00
}
}
2023-02-01 10:09:20 -05:00
fs.files.Range(func(key string, file *memFile) bool {
if file.dir {
return true
2022-05-13 13:26:45 -04:00
}
if !strings.HasPrefix(file.name, path) {
return true
}
if compiledPattern != nil {
if !compiledPattern.Match(file.name) {
return true
}
2023-02-01 10:09:20 -05:00
}
2022-05-13 13:26:45 -04:00
files = append(files, &memFileInfo{
name: file.name,
size: file.size,
lastMod: file.lastMod,
linkTo: file.linkTo,
})
return true
})
2022-05-13 13:26:45 -04:00
return files
}
2023-02-01 10:09:20 -05:00
2023-02-21 06:57:33 -05:00
func (fs *memFilesystem) LookPath(file string) (string, error) {
if strings.Contains(file, "/") {
file = fs.cleanPath(file)
info, err := fs.Stat(file)
if err == nil {
if !info.Mode().IsRegular() {
return file, os.ErrNotExist
}
return file, nil
}
return "", os.ErrNotExist
}
path := os.Getenv("PATH")
for _, dir := range filepath.SplitList(path) {
if dir == "" {
// Unix shell semantics: path element "" means "."
dir = "."
}
path := filepath.Join(dir, file)
path = fs.cleanPath(path)
if info, err := fs.Stat(path); err == nil {
if !filepath.IsAbs(path) {
return path, os.ErrNotExist
}
if !info.Mode().IsRegular() {
return path, os.ErrNotExist
}
return path, nil
}
}
return "", os.ErrNotExist
}
2023-02-01 10:09:20 -05:00
func (fs *memFilesystem) cleanPath(path string) string {
if !filepath.IsAbs(path) {
path = filepath.Join("/", path)
}
return filepath.Join("/", filepath.Clean(path))
}