one store type: DirStore is the only concrete Store implementation
- RunFileStore replaced by NewFileStore() returning *DirStore - SessionStore now embeds *DirStore with hooks - Removed: storeConfig, RunnableStore, RunFileStore, runfilestore.go - New filestore.go: NewFileStore builds DirStore from FileSpec table - DirStore.Stat uses opener for synthetic files - Runnable concern stays on Session (not on store)
This commit is contained in:
parent
30e171c1ba
commit
f5841c4a0f
|
|
@ -28,7 +28,6 @@ import (
|
||||||
type (
|
type (
|
||||||
StoreEntry = store.StoreEntry
|
StoreEntry = store.StoreEntry
|
||||||
Store = store.Store
|
Store = store.Store
|
||||||
RunnableStore = store.RunnableStore
|
|
||||||
Session = store.Session
|
Session = store.Session
|
||||||
SessionStore = store.SessionStore
|
SessionStore = store.SessionStore
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,6 @@ func (d *DirStore) Stat(name string) (os.FileInfo, error) {
|
||||||
// Check children first.
|
// Check children first.
|
||||||
if child, rest, ok := d.split(name); ok {
|
if child, rest, ok := d.split(name); ok {
|
||||||
if rest == "" {
|
if rest == "" {
|
||||||
// The child directory itself.
|
|
||||||
return &SyntheticFileInfo{Name_: name, Mode_: 0555, IsDir_: true}, nil
|
return &SyntheticFileInfo{Name_: name, Mode_: 0555, IsDir_: true}, nil
|
||||||
}
|
}
|
||||||
return child.Stat(rest)
|
return child.Stat(rest)
|
||||||
|
|
@ -156,6 +155,14 @@ func (d *DirStore) Stat(name string) (os.FileInfo, error) {
|
||||||
if name == "idx" && d.indexFn != nil {
|
if name == "idx" && d.indexFn != nil {
|
||||||
return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil
|
return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil
|
||||||
}
|
}
|
||||||
|
// Synthetic files: use opener for stat.
|
||||||
|
if d.openFn != nil {
|
||||||
|
e, err := d.openFn(d.dirs, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.Stat()
|
||||||
|
}
|
||||||
p, err := d.resolve(name)
|
p, err := d.resolve(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileSpec describes a single synthetic file exposed by a DirStore.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileStore creates a DirStore backed by a table of FileSpecs (synthetic files).
|
||||||
|
func NewFileStore(specs []FileSpec) *DirStore {
|
||||||
|
index := make(map[string]int, len(specs))
|
||||||
|
for i, s := range specs {
|
||||||
|
index[s.Name] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup := func(name string) (*FileSpec, bool) {
|
||||||
|
i, ok := index[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return &specs[i], true
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewDirStore(nil, 0,
|
||||||
|
WithLister(func(_ []string) ([]os.DirEntry, error) {
|
||||||
|
entries := make([]os.DirEntry, len(specs))
|
||||||
|
for i, s := range specs {
|
||||||
|
entries[i] = FileEntry(s.Name, s.Mode)
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}),
|
||||||
|
WithOpener(func(_ []string, name string) (StoreEntry, error) {
|
||||||
|
spec, ok := lookup(name)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s: not found", name)
|
||||||
|
}
|
||||||
|
writeFn := func([]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) {
|
||||||
|
var size int64
|
||||||
|
if spec.Size != nil {
|
||||||
|
size = spec.Size()
|
||||||
|
} else if spec.Wait != nil {
|
||||||
|
size = 4096
|
||||||
|
} else if data, err := spec.Read(); err == nil {
|
||||||
|
size = int64(len(data))
|
||||||
|
}
|
||||||
|
return &SyntheticFileInfo{Name_: name, Mode_: spec.Mode, Size_: size}, nil
|
||||||
|
},
|
||||||
|
ReadFn: spec.Read,
|
||||||
|
WriteFn: writeFn,
|
||||||
|
BlockingReadFn: waitFn,
|
||||||
|
OneShot_: spec.OneShot || spec.Wait != nil,
|
||||||
|
IsBlocking_: spec.Wait != nil,
|
||||||
|
Async_: spec.Async,
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -124,7 +124,7 @@ type SessionStoreConfig struct {
|
||||||
|
|
||||||
// SessionStore implements Store for session management.
|
// SessionStore implements Store for session management.
|
||||||
type SessionStore struct {
|
type SessionStore struct {
|
||||||
*storeConfig
|
*DirStore
|
||||||
cfg SessionStoreConfig
|
cfg SessionStoreConfig
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
sessions map[string]*Session
|
sessions map[string]*Session
|
||||||
|
|
@ -148,14 +148,13 @@ func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
|
||||||
sessions: make(map[string]*Session),
|
sessions: make(map[string]*Session),
|
||||||
}
|
}
|
||||||
ss.nextUID.Store(9999)
|
ss.nextUID.Store(9999)
|
||||||
ss.storeConfig = &storeConfig{
|
ss.DirStore = NewDirStore(nil, 0,
|
||||||
StatFn: ss.stat,
|
WithOpener(func(_ []string, name string) (StoreEntry, error) { return ss.openEntry(name) }),
|
||||||
ListFn: ss.list,
|
WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
|
||||||
OpenFn: ss.openEntry,
|
WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
|
||||||
DeleteFn: ss.del,
|
WithCreator(func(_ []string, name string, _ os.FileMode) error { return ss.create(name) }),
|
||||||
CreateFn: ss.create,
|
WithRenamer(func(_ []string, old, new string) error { return ss.renameSession(old, new) }),
|
||||||
RenameFn: ss.renameSession,
|
)
|
||||||
}
|
|
||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,6 +215,10 @@ func (s *SessionStore) ListDir(name string) ([]os.DirEntry, error) {
|
||||||
return nil, fmt.Errorf("%s: not a directory", name)
|
return nil, fmt.Errorf("%s: not a directory", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SessionStore) Stat(name string) (os.FileInfo, error) {
|
||||||
|
return s.stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SessionStore) stat(name string) (os.FileInfo, error) {
|
func (s *SessionStore) stat(name string) (os.FileInfo, error) {
|
||||||
// Top-level fixed files (new, idx, sh, etc.)
|
// Top-level fixed files (new, idx, sh, etc.)
|
||||||
if mode, ok := sessionStoreFiles[name]; ok {
|
if mode, ok := sessionStoreFiles[name]; ok {
|
||||||
|
|
@ -428,15 +431,15 @@ func (s *SessionStore) SessionByUname(uname string) *Session {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenStore returns a RunnableStore for the given session ID.
|
// OpenStore returns a DirStore for the given session ID.
|
||||||
func (s *SessionStore) OpenStore(id string) (RunnableStore, error) {
|
func (s *SessionStore) OpenStore(id string) (*DirStore, error) {
|
||||||
if sess := s.Session(id); sess != nil {
|
if sess := s.Session(id); sess != nil {
|
||||||
return s.openStore(sess)
|
return s.openStore(sess)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("session not found: %s", id)
|
return nil, fmt.Errorf("session not found: %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) openStore(sess *Session) (RunnableStore, error) {
|
func (s *SessionStore) openStore(sess *Session) (*DirStore, error) {
|
||||||
id := sess.RunnableID()
|
id := sess.RunnableID()
|
||||||
return NewSessionFileStore(
|
return NewSessionFileStore(
|
||||||
sess,
|
sess,
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ var SessionFileList = []struct {
|
||||||
{"context", 0444, false, false},
|
{"context", 0444, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionFileStore is a RunFileStore for a session directory.
|
// SessionFileStore is a DirStore for a session directory.
|
||||||
type SessionFileStore = RunFileStore
|
type SessionFileStore = DirStore
|
||||||
|
|
||||||
func NewSessionFileStore(sess *Session, log *olog.Logger, kill func(), rename func(newID string) error, saveTranscript func([]byte) error) *SessionFileStore {
|
func NewSessionFileStore(sess *Session, log *olog.Logger, kill func(), rename func(newID string) error, saveTranscript func([]byte) error) *SessionFileStore {
|
||||||
h := &sessionHelper{sess: sess, log: log, kill: kill, rename: rename, saveTranscript: saveTranscript}
|
h := &sessionHelper{sess: sess, log: log, kill: kill, rename: rename, saveTranscript: saveTranscript}
|
||||||
|
|
@ -52,7 +52,7 @@ func NewSessionFileStore(sess *Session, log *olog.Logger, kill func(), rename fu
|
||||||
specs[i].OneShot = f.OneShot
|
specs[i].OneShot = f.OneShot
|
||||||
specs[i].Async = f.Async
|
specs[i].Async = f.Async
|
||||||
}
|
}
|
||||||
return NewRunFileStore(sess, specs)
|
return NewFileStore(specs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sessionHelper holds the dependencies needed to build session FileSpecs.
|
// sessionHelper holds the dependencies needed to build session FileSpecs.
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,6 @@ type Runnable interface {
|
||||||
LogInfo() (length int, vers uint32)
|
LogInfo() (length int, vers uint32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunnableStore is a Store backed by a running agent.
|
|
||||||
type RunnableStore interface {
|
|
||||||
Store
|
|
||||||
Runnable
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntryConfig implements StoreEntry via function pointers.
|
// EntryConfig implements StoreEntry via function pointers.
|
||||||
type EntryConfig struct {
|
type EntryConfig struct {
|
||||||
StatFn func() (os.FileInfo, error)
|
StatFn func() (os.FileInfo, error)
|
||||||
|
|
@ -68,23 +62,6 @@ func (e *EntryConfig) OneShot() bool { return e.OneShot_ }
|
||||||
func (e *EntryConfig) IsBlocking() bool { return e.IsBlocking_ }
|
func (e *EntryConfig) IsBlocking() bool { return e.IsBlocking_ }
|
||||||
func (e *EntryConfig) Async() bool { return e.Async_ }
|
func (e *EntryConfig) Async() bool { return e.Async_ }
|
||||||
|
|
||||||
// storeConfig implements Store via function pointers.
|
|
||||||
type storeConfig struct {
|
|
||||||
StatFn func(string) (os.FileInfo, error)
|
|
||||||
ListFn func() ([]os.DirEntry, error)
|
|
||||||
OpenFn func(string) (StoreEntry, error)
|
|
||||||
CreateFn func(string) error
|
|
||||||
DeleteFn func(string) error
|
|
||||||
RenameFn func(string, string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storeConfig) Stat(name string) (os.FileInfo, error) { return s.StatFn(name) }
|
|
||||||
func (s *storeConfig) List() ([]os.DirEntry, error) { return s.ListFn() }
|
|
||||||
func (s *storeConfig) Open(name string) (StoreEntry, error) { return s.OpenFn(name) }
|
|
||||||
func (s *storeConfig) Create(name string) error { return s.CreateFn(name) }
|
|
||||||
func (s *storeConfig) Delete(name string) error { return s.DeleteFn(name) }
|
|
||||||
func (s *storeConfig) Rename(old, new string) error { return s.RenameFn(old, new) }
|
|
||||||
|
|
||||||
// SyntheticFileInfo implements os.FileInfo for entries with no backing file.
|
// SyntheticFileInfo implements os.FileInfo for entries with no backing file.
|
||||||
type SyntheticFileInfo struct {
|
type SyntheticFileInfo struct {
|
||||||
Name_ string
|
Name_ string
|
||||||
|
|
|
||||||
Reference in New Issue