For the API endpoint /v3/process two new query parameter are introduced in order to list only processes that match a pattern for the id and the reference: idpattern and refpattern. The pattern is a glob pattern. If patterns for both are given, the results will be intersected. If you use other query parameters such as id or reference, they will be applied after the result of the pattern matching.
335 lines
5.9 KiB
Go
335 lines
5.9 KiB
Go
package fs
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/datarhei/core/v16/glob"
|
|
"github.com/datarhei/core/v16/log"
|
|
)
|
|
|
|
// DiskConfig is the config required to create a new disk
|
|
// filesystem.
|
|
type DiskConfig struct {
|
|
// Dir is the path to the directory to observe
|
|
Dir string
|
|
|
|
// Size of the filesystem in bytes
|
|
Size int64
|
|
|
|
// For logging, optional
|
|
Logger log.Logger
|
|
}
|
|
|
|
// diskFileInfo implements the FileInfo interface
|
|
type diskFileInfo struct {
|
|
dir string
|
|
name string
|
|
finfo os.FileInfo
|
|
}
|
|
|
|
func (fi *diskFileInfo) Name() string {
|
|
return fi.name
|
|
}
|
|
|
|
func (fi *diskFileInfo) Size() int64 {
|
|
return fi.finfo.Size()
|
|
}
|
|
|
|
func (fi *diskFileInfo) ModTime() time.Time {
|
|
return fi.finfo.ModTime()
|
|
}
|
|
|
|
func (fi *diskFileInfo) IsLink() (string, bool) {
|
|
mode := fi.finfo.Mode()
|
|
if mode&os.ModeSymlink == 0 {
|
|
return fi.name, false
|
|
}
|
|
|
|
path, err := os.Readlink(filepath.Join(fi.dir, fi.name))
|
|
if err != nil {
|
|
return fi.name, false
|
|
}
|
|
|
|
path = filepath.Join(fi.dir, path)
|
|
|
|
if !strings.HasPrefix(path, fi.dir) {
|
|
return fi.name, false
|
|
}
|
|
|
|
name := strings.TrimPrefix(path, fi.dir)
|
|
if name[0] != os.PathSeparator {
|
|
name = string(os.PathSeparator) + name
|
|
}
|
|
|
|
return name, true
|
|
}
|
|
|
|
func (fi *diskFileInfo) IsDir() bool {
|
|
return fi.finfo.IsDir()
|
|
}
|
|
|
|
// diskFile implements the File interface
|
|
type diskFile struct {
|
|
dir string
|
|
name string
|
|
file *os.File
|
|
}
|
|
|
|
func (f *diskFile) Name() string {
|
|
return f.name
|
|
}
|
|
|
|
func (f *diskFile) Stat() (FileInfo, error) {
|
|
finfo, err := f.file.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dif := &diskFileInfo{
|
|
dir: f.dir,
|
|
name: f.name,
|
|
finfo: finfo,
|
|
}
|
|
|
|
return dif, nil
|
|
}
|
|
|
|
func (f *diskFile) Close() error {
|
|
return f.file.Close()
|
|
}
|
|
|
|
func (f *diskFile) Read(p []byte) (int, error) {
|
|
return f.file.Read(p)
|
|
}
|
|
|
|
// diskFilesystem implements the Filesystem interface
|
|
type diskFilesystem struct {
|
|
dir string
|
|
|
|
// Max. size of the filesystem in bytes as
|
|
// given by the config
|
|
maxSize int64
|
|
|
|
// Current size of the filesystem in bytes
|
|
currentSize int64
|
|
lastSizeCheck time.Time
|
|
|
|
// Logger from the config
|
|
logger log.Logger
|
|
}
|
|
|
|
// NewDiskFilesystem returns a new filesystem that is backed by a disk
|
|
// that implements the Filesystem interface
|
|
func NewDiskFilesystem(config DiskConfig) (Filesystem, error) {
|
|
fs := &diskFilesystem{
|
|
maxSize: config.Size,
|
|
logger: config.Logger,
|
|
}
|
|
|
|
if fs.logger == nil {
|
|
fs.logger = log.New("DiskFS")
|
|
}
|
|
|
|
if err := fs.Rebase(config.Dir); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fs, nil
|
|
}
|
|
|
|
func (fs *diskFilesystem) Base() string {
|
|
return fs.dir
|
|
}
|
|
|
|
func (fs *diskFilesystem) Rebase(base string) error {
|
|
if len(base) == 0 {
|
|
return fmt.Errorf("invalid base path provided")
|
|
}
|
|
|
|
dir, err := filepath.Abs(base)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
base = dir
|
|
|
|
finfo, err := os.Stat(base)
|
|
if err != nil {
|
|
return fmt.Errorf("the provided base path '%s' doesn't exist", fs.dir)
|
|
}
|
|
|
|
if !finfo.IsDir() {
|
|
return fmt.Errorf("the provided base path '%s' must be a directory", fs.dir)
|
|
}
|
|
|
|
fs.dir = base
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fs *diskFilesystem) Size() (int64, int64) {
|
|
// This is to cache the size for some time in order not to
|
|
// stress the underlying filesystem too much.
|
|
if time.Since(fs.lastSizeCheck) >= 10*time.Second {
|
|
var size int64 = 0
|
|
|
|
fs.walk(func(path string, info os.FileInfo) {
|
|
size += info.Size()
|
|
})
|
|
|
|
fs.currentSize = size
|
|
|
|
fs.lastSizeCheck = time.Now()
|
|
}
|
|
|
|
return fs.currentSize, fs.maxSize
|
|
}
|
|
|
|
func (fs *diskFilesystem) Resize(size int64) {
|
|
fs.maxSize = size
|
|
}
|
|
|
|
func (fs *diskFilesystem) Files() int64 {
|
|
var nfiles int64 = 0
|
|
|
|
fs.walk(func(path string, info os.FileInfo) {
|
|
nfiles++
|
|
})
|
|
|
|
return nfiles
|
|
}
|
|
|
|
func (fs *diskFilesystem) Symlink(oldname, newname string) error {
|
|
oldname = filepath.Join(fs.dir, filepath.Clean("/"+oldname))
|
|
|
|
if !filepath.IsAbs(newname) {
|
|
return nil
|
|
}
|
|
|
|
newname = filepath.Join(fs.dir, filepath.Clean("/"+newname))
|
|
|
|
err := os.Symlink(oldname, newname)
|
|
|
|
return err
|
|
}
|
|
|
|
func (fs *diskFilesystem) Open(path string) File {
|
|
path = filepath.Join(fs.dir, filepath.Clean("/"+path))
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
df := &diskFile{
|
|
dir: fs.dir,
|
|
name: path,
|
|
file: f,
|
|
}
|
|
|
|
return df
|
|
}
|
|
|
|
func (fs *diskFilesystem) Store(path string, r io.Reader) (int64, bool, error) {
|
|
path = filepath.Join(fs.dir, filepath.Clean("/"+path))
|
|
|
|
replace := true
|
|
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return -1, false, fmt.Errorf("creating file failed: %w", err)
|
|
}
|
|
|
|
var f *os.File
|
|
var err error
|
|
|
|
f, err = os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0)
|
|
if err != nil {
|
|
f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return -1, false, fmt.Errorf("creating file failed: %w", err)
|
|
}
|
|
|
|
replace = false
|
|
}
|
|
|
|
size, err := f.ReadFrom(r)
|
|
if err != nil {
|
|
return -1, false, fmt.Errorf("reading data failed: %w", err)
|
|
}
|
|
|
|
return size, !replace, nil
|
|
}
|
|
|
|
func (fs *diskFilesystem) Delete(path string) int64 {
|
|
path = filepath.Join(fs.dir, filepath.Clean("/"+path))
|
|
|
|
finfo, err := os.Stat(path)
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
|
|
size := finfo.Size()
|
|
|
|
if err := os.Remove(path); err != nil {
|
|
return -1
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
func (fs *diskFilesystem) DeleteAll() int64 {
|
|
return 0
|
|
}
|
|
|
|
func (fs *diskFilesystem) List(pattern string) []FileInfo {
|
|
files := []FileInfo{}
|
|
|
|
fs.walk(func(path string, info os.FileInfo) {
|
|
name := strings.TrimPrefix(path, fs.dir)
|
|
if name[0] != os.PathSeparator {
|
|
name = string(os.PathSeparator) + name
|
|
}
|
|
|
|
if len(pattern) != 0 {
|
|
if ok, _ := glob.Match(pattern, name, '/'); !ok {
|
|
return
|
|
}
|
|
}
|
|
|
|
files = append(files, &diskFileInfo{
|
|
dir: fs.dir,
|
|
name: name,
|
|
finfo: info,
|
|
})
|
|
})
|
|
|
|
return files
|
|
}
|
|
|
|
func (fs *diskFilesystem) walk(walkfn func(path string, info os.FileInfo)) {
|
|
filepath.Walk(fs.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() && mode&os.ModeSymlink == 0 {
|
|
return nil
|
|
}
|
|
|
|
walkfn(path, info)
|
|
|
|
return nil
|
|
})
|
|
}
|