This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-9p/store/runfilestore.go

111 lines
3.1 KiB
Go

package store
import (
"context"
"fmt"
"os"
)
// FileSpec describes a single synthetic file exposed by a RunFileStore.
type FileSpec struct {
Name string
Mode os.FileMode
Read func() ([]byte, error)
Write func([]byte) error // nil = read-only
Wait func(ctx context.Context, base string) (content []byte, nextBase string, err error) // nil = not waitable
Size func() int64 // optional; if nil, len(Read())
OneShot bool // true = yields data once per open
Async bool // true = writes dispatched asynchronously
}
// RunFileStore implements RunnableStore for any Runnable using a table of FileSpecs.
type RunFileStore struct {
*storeConfig
Runnable
specs []FileSpec
index map[string]int // name -> index into specs
}
// NewRunFileStore creates a RunnableStore backed by the given Runnable and file table.
func NewRunFileStore(r Runnable, specs []FileSpec) *RunFileStore {
rs := &RunFileStore{
Runnable: r,
specs: specs,
index: make(map[string]int, len(specs)),
}
for i, s := range specs {
rs.index[s.Name] = i
}
notSupported := func(string) error { return fmt.Errorf("not supported") }
rs.storeConfig = &storeConfig{
StatFn: rs.stat,
ListFn: rs.list,
OpenFn: rs.open,
DeleteFn: notSupported,
CreateFn: notSupported,
RenameFn: func(string, string) error { return fmt.Errorf("not supported") },
}
return rs
}
func (rs *RunFileStore) lookup(name string) (*FileSpec, bool) {
i, ok := rs.index[name]
if !ok {
return nil, false
}
return &rs.specs[i], true
}
func (rs *RunFileStore) stat(name string) (os.FileInfo, error) {
spec, ok := rs.lookup(name)
if !ok {
return nil, fmt.Errorf("%s: not found", name)
}
var size int64
if spec.Size != nil {
size = spec.Size()
} else if spec.Wait != nil {
// Wait files block on read; report large size so clients read fully.
size = 4096
} else {
if data, err := spec.Read(); err == nil {
size = int64(len(data))
}
}
return &SyntheticFileInfo{Name_: name, Mode_: spec.Mode, Size_: size}, nil
}
func (rs *RunFileStore) list() ([]os.DirEntry, error) {
entries := make([]os.DirEntry, len(rs.specs))
for i, s := range rs.specs {
entries[i] = FileEntry(s.Name, s.Mode)
}
return entries, nil
}
func (rs *RunFileStore) open(name string) (StoreEntry, error) {
spec, ok := rs.lookup(name)
if !ok {
return nil, fmt.Errorf("%s: not found", name)
}
writeFn := func(data []byte) error { return fmt.Errorf("%s: read-only", name) }
if spec.Write != nil {
writeFn = spec.Write
}
waitFn := func(context.Context, string) ([]byte, string, error) {
return nil, "", fmt.Errorf("%s: not a wait file", name)
}
if spec.Wait != nil {
waitFn = spec.Wait
}
return &EntryConfig{
StatFn: func() (os.FileInfo, error) { return rs.stat(name) },
ReadFn: spec.Read,
WriteFn: writeFn,
BlockingReadFn: waitFn,
OneShot_: spec.OneShot || spec.Wait != nil,
IsBlocking_: spec.Wait != nil,
Async_: spec.Async,
}, nil
}