rename to 9P vocabulary: store/ → fs/, DirStore → Tree, StoreEntry → File

- Package: store → fs
- Type: DirStore → Tree, StoreEntry → File, EntryConfig → FileConfig
- Constructor: NewDirStore → NewTree, NewFileStore → NewSynth
- Methods: ListDir → Readdir, WithListDir → WithReaddir
- Interface: Store → FileTree
- Files: dirstore.go → tree.go, filestore.go → synth.go, store.go → fs.go
- Test: store_test.go → fs_test.go
This commit is contained in:
Levi Neely 2026-05-19 18:19:37 +02:00
parent 56bcb87ff6
commit a9e9538015
12 changed files with 233 additions and 233 deletions

View File

@ -1,6 +1,6 @@
// Package store defines storage interfaces and a filesystem-backed
// implementation used by the 9P server.
package store
package fs
import (
"context"
@ -8,8 +8,8 @@ import (
"time"
)
// StoreEntry is an open handle to a single named blob.
type StoreEntry interface {
// File is an open handle to a single named blob.
type File interface {
Stat() (os.FileInfo, error)
Read() ([]byte, error)
Write(data []byte) error
@ -22,11 +22,11 @@ type StoreEntry interface {
Async() bool
}
// Store is a named collection of entries.
type Store interface {
// FileTree is a named collection of entries.
type FileTree interface {
Stat(name string) (os.FileInfo, error)
List() ([]os.DirEntry, error)
Open(name string) (StoreEntry, error)
Open(name string) (File, error)
Create(name string) error
Delete(name string) error
Rename(oldName, newName string) error
@ -41,8 +41,8 @@ type Runnable interface {
LogInfo() (length int, vers uint32)
}
// EntryConfig implements StoreEntry via function pointers.
type EntryConfig struct {
// FileConfig implements File via function pointers.
type FileConfig struct {
StatFn func() (os.FileInfo, error)
ReadFn func() ([]byte, error)
WriteFn func([]byte) error
@ -52,15 +52,15 @@ type EntryConfig struct {
Async_ bool
}
func (e *EntryConfig) Stat() (os.FileInfo, error) { return e.StatFn() }
func (e *EntryConfig) Read() ([]byte, error) { return e.ReadFn() }
func (e *EntryConfig) Write(data []byte) error { return e.WriteFn(data) }
func (e *EntryConfig) BlockingRead(ctx context.Context, base string) ([]byte, string, error) {
func (e *FileConfig) Stat() (os.FileInfo, error) { return e.StatFn() }
func (e *FileConfig) Read() ([]byte, error) { return e.ReadFn() }
func (e *FileConfig) Write(data []byte) error { return e.WriteFn(data) }
func (e *FileConfig) BlockingRead(ctx context.Context, base string) ([]byte, string, error) {
return e.BlockingReadFn(ctx, base)
}
func (e *EntryConfig) OneShot() bool { return e.OneShot_ }
func (e *EntryConfig) IsBlocking() bool { return e.IsBlocking_ }
func (e *EntryConfig) Async() bool { return e.Async_ }
func (e *FileConfig) OneShot() bool { return e.OneShot_ }
func (e *FileConfig) IsBlocking() bool { return e.IsBlocking_ }
func (e *FileConfig) Async() bool { return e.Async_ }
// SyntheticFileInfo implements os.FileInfo for entries with no backing file.
type SyntheticFileInfo struct {

View File

@ -1,4 +1,4 @@
package store_test
package fs_test
import (
"fmt"
@ -9,7 +9,7 @@ import (
"testing"
olog "ollie/pkg/log"
"olliesrv/store"
fs "olliesrv/fs"
)
// --- stub agent.Core ---
@ -28,7 +28,7 @@ func seedSkill(t *testing.T, base, name string) {
}
// storeRead is a test helper: Open + Read.
func storeRead(t *testing.T, s store.Store, name string) []byte {
func storeRead(t *testing.T, s fs.FileTree, name string) []byte {
t.Helper()
e, err := s.Open(name)
if err != nil {
@ -42,7 +42,7 @@ func storeRead(t *testing.T, s store.Store, name string) []byte {
}
// storeWrite is a test helper: Open + Write.
func storeWrite(t *testing.T, s store.Store, name string, data []byte) {
func storeWrite(t *testing.T, s fs.FileTree, name string, data []byte) {
t.Helper()
e, err := s.Open(name)
if err != nil {
@ -55,7 +55,7 @@ func storeWrite(t *testing.T, s store.Store, name string, data []byte) {
// --- contract checks ---
func checkReadableContract(t *testing.T, s store.Store, name string) {
func checkReadableContract(t *testing.T, s fs.FileTree, name string) {
t.Helper()
fi, err := s.Stat(name)
if err != nil {
@ -89,7 +89,7 @@ func checkReadableContract(t *testing.T, s store.Store, name string) {
}
}
func checkReadWriteContract(t *testing.T, s store.Store, name string) {
func checkReadWriteContract(t *testing.T, s fs.FileTree, name string) {
t.Helper()
want := []byte("hello")
e, err := s.Open(name)
@ -120,7 +120,7 @@ func checkReadWriteContract(t *testing.T, s store.Store, name string) {
checkReadableContract(t, s, name)
}
func checkStoreContract(t *testing.T, s store.Store, name string) {
func checkStoreContract(t *testing.T, s fs.FileTree, name string) {
t.Helper()
if err := s.Create(name); err != nil {
t.Fatalf("Create: %v", err)
@ -157,18 +157,18 @@ func checkStoreContract(t *testing.T, s store.Store, name string) {
// ===== FlatDir =====
func TestFlatDirContract(t *testing.T) {
checkStoreContract(t, store.NewDirStore([]string{t.TempDir()}, 0644), "test-file")
checkStoreContract(t, fs.NewTree([]string{t.TempDir()}, 0644), "test-file")
}
func TestFlatDirCreateMkdirError(t *testing.T) {
fd := store.NewDirStore([]string{"/nonexistent/path"}, 0644)
fd := fs.NewTree([]string{"/nonexistent/path"}, 0644)
if err := fd.Create("f"); err == nil {
t.Error("Create should fail when dir doesn't exist")
}
}
func TestFlatDirPutMkdirError(t *testing.T) {
fd := store.NewDirStore([]string{"/nonexistent/path"}, 0644)
fd := fs.NewTree([]string{"/nonexistent/path"}, 0644)
e, err := fd.Open("f")
if err != nil {
t.Fatalf("Open: %v", err)
@ -185,7 +185,7 @@ func TestSkillStoreContract(t *testing.T) {
t.Setenv("OLLIE_SKILLS_PATH", dir)
seedSkill(t, dir, "test-skill")
s := store.NewSkillStore()
s := fs.NewSkillStore()
// Rename (needs seeded skill with valid front matter)
seedSkill(t, dir, "rename-src")
@ -239,7 +239,7 @@ func TestSkillStoreContract(t *testing.T) {
// Create calls through injected mkdirAll + writeFile
var createdDir, createdFile string
s2 := store.NewSkillStoreWith(store.SkillStoreConfig{
s2 := fs.NewSkillStoreWith(fs.SkillStoreConfig{
Dirs: []string{dir},
MkdirAll: func(p string, _ os.FileMode) error { createdDir = p; return nil },
WriteFile: func(p string, _ []byte, _ os.FileMode) error { createdFile = p; return nil },
@ -255,7 +255,7 @@ func TestSkillStoreContract(t *testing.T) {
}
// Create mkdirAll error
s3 := store.NewSkillStoreWith(store.SkillStoreConfig{
s3 := fs.NewSkillStoreWith(fs.SkillStoreConfig{
Dirs: []string{dir},
MkdirAll: func(string, os.FileMode) error { return fmt.Errorf("denied") },
})

View File

@ -1,4 +1,4 @@
package store
package fs
import (
"context"
@ -7,7 +7,7 @@ import (
"sync"
)
// MemoryStore is an in-memory key/value store implementing Store.
// MemoryStore is an in-memory key/value store implementing FileTree.
// Values are []byte blobs held in RAM; nothing is persisted to disk.
// All operations are safe for concurrent use.
type MemoryStore struct {
@ -34,7 +34,7 @@ func (m *MemoryStore) Set(key string, value []byte) {
m.mu.Unlock()
}
// --- Store interface ---
// --- FileTree interface ---
func (m *MemoryStore) Stat(name string) (os.FileInfo, error) {
m.mu.RLock()
@ -57,11 +57,11 @@ func (m *MemoryStore) List() ([]os.DirEntry, error) {
return entries, nil
}
func (m *MemoryStore) Open(name string) (StoreEntry, error) {
func (m *MemoryStore) Open(name string) (File, error) {
notBlocking := func(context.Context, string) ([]byte, string, error) {
return nil, "", fmt.Errorf("blocking read not supported")
}
return &EntryConfig{
return &FileConfig{
StatFn: func() (os.FileInfo, error) { return m.Stat(name) },
ReadFn: func() ([]byte, error) {
m.mu.RLock()

View File

@ -1,4 +1,4 @@
package store
package fs
import (
"context"
@ -10,8 +10,8 @@ import (
"ollie/pkg/paths"
)
// NewRootStore returns a read-only Store for synthetic root-level files.
func NewRootStore() *DirStore {
// NewRootStore returns a read-only FileTree for synthetic root-level files.
func NewRootStore() *Tree {
helpPath := paths.CfgDir() + "/help.md"
entries := map[string]func() ([]byte, error){
"backends": func() ([]byte, error) {
@ -26,7 +26,7 @@ func NewRootStore() *DirStore {
}
readOnly := func([]byte) error { return fmt.Errorf("read-only") }
return NewDirStore(nil, 0444,
return NewTree(nil, 0444,
WithReadOnly(),
WithResolver(func(_ []string, name string) (string, error) {
if _, ok := entries[name]; ok {
@ -40,12 +40,12 @@ func NewRootStore() *DirStore {
FileEntry("help", 0444),
}, nil
}),
WithOpener(func(_ []string, name string) (StoreEntry, error) {
WithOpener(func(_ []string, name string) (File, error) {
readFn, ok := entries[name]
if !ok {
return nil, fmt.Errorf("%s: not found", name)
}
return &EntryConfig{
return &FileConfig{
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: name, Mode_: 0444}, nil },
ReadFn: readFn,
WriteFn: readOnly,

View File

@ -1,4 +1,4 @@
package store
package fs
import (
"fmt"
@ -11,9 +11,9 @@ import (
"ollie/pkg/skills"
)
// NewSkillStore returns a DirStore configured for the skill directory layout.
// NewSkillStore returns a Tree configured for the skill directory layout.
// Skills are directories containing SKILL.md; virtual names are "skillname.md".
func NewSkillStore() *DirStore {
func NewSkillStore() *Tree {
return NewSkillStoreWith(SkillStoreConfig{})
}
@ -29,7 +29,7 @@ type SkillStoreConfig struct {
Rename func(string, string) error
}
func NewSkillStoreWith(cfg SkillStoreConfig) *DirStore {
func NewSkillStoreWith(cfg SkillStoreConfig) *Tree {
if cfg.Dirs == nil {
cfg.Dirs = skillDirs()
}
@ -57,7 +57,7 @@ func NewSkillStoreWith(cfg SkillStoreConfig) *DirStore {
ss := &skillHelper{cfg: cfg}
return NewDirStore(cfg.Dirs, 0644,
return NewTree(cfg.Dirs, 0644,
WithIndex(ss.index),
WithResolver(ss.resolve),
WithLister(ss.list),
@ -157,7 +157,7 @@ func (ss *skillHelper) rename(_ []string, old, new string) error {
return fmt.Errorf("skill not found: %s", oldSkill)
}
func (ss *skillHelper) index(_ Store) ([]byte, error) {
func (ss *skillHelper) index(_ FileTree) ([]byte, error) {
var sb strings.Builder
for _, m := range ss.listSkills() {
fmt.Fprintf(&sb, "## %s\n", m.Name)

View File

@ -1,4 +1,4 @@
package store
package fs
import (
"context"
@ -6,7 +6,7 @@ import (
"os"
)
// FileSpec describes a single synthetic file exposed by a DirStore.
// FileSpec describes a single synthetic file exposed by a Tree.
type FileSpec struct {
Name string
Mode os.FileMode
@ -18,8 +18,8 @@ type FileSpec struct {
Async bool // true = writes dispatched asynchronously
}
// NewFileStore creates a DirStore backed by a table of FileSpecs (synthetic files).
func NewFileStore(specs []FileSpec) *DirStore {
// NewSynth creates a Tree backed by a table of FileSpecs (synthetic files).
func NewSynth(specs []FileSpec) *Tree {
index := make(map[string]int, len(specs))
for i, s := range specs {
index[s.Name] = i
@ -33,7 +33,7 @@ func NewFileStore(specs []FileSpec) *DirStore {
return &specs[i], true
}
return NewDirStore(nil, 0,
return NewTree(nil, 0,
WithLister(func(_ []string) ([]os.DirEntry, error) {
entries := make([]os.DirEntry, len(specs))
for i, s := range specs {
@ -41,7 +41,7 @@ func NewFileStore(specs []FileSpec) *DirStore {
}
return entries, nil
}),
WithOpener(func(_ []string, name string) (StoreEntry, error) {
WithOpener(func(_ []string, name string) (File, error) {
spec, ok := lookup(name)
if !ok {
return nil, fmt.Errorf("%s: not found", name)
@ -56,7 +56,7 @@ func NewFileStore(specs []FileSpec) *DirStore {
if spec.Wait != nil {
waitFn = spec.Wait
}
return &EntryConfig{
return &FileConfig{
StatFn: func() (os.FileInfo, error) {
var size int64
if spec.Size != nil {

View File

@ -1,4 +1,4 @@
package store
package fs
import (
"fmt"
@ -6,7 +6,7 @@ import (
)
// ToolIndex generates a tool index from a store's file listing.
func ToolIndex(s Store) ([]byte, error) {
func ToolIndex(s FileTree) ([]byte, error) {
entries, err := s.List()
if err != nil {
return nil, err

View File

@ -1,4 +1,4 @@
package store
package fs
import (
"context"
@ -7,103 +7,103 @@ import (
"path/filepath"
)
// DirStore is a configurable filesystem-backed Store.
// Tree is a configurable filesystem-backed FileTree.
// It supports single or multiple directories (union), read-only mode,
// a synthetic index entry, subdirectory operations, custom path resolution,
// and mounting child DirStores at names (sub-stores).
type DirStore struct {
dirs []string
type Tree struct {
Tree []string
perm os.FileMode
readOnly bool
indexFn func(Store) ([]byte, error)
indexFn func(FileTree) ([]byte, error)
resolveFn func([]string, string) (string, error)
listFn func([]string) ([]os.DirEntry, error)
createFn func([]string, string, os.FileMode) error
deleteFn func([]string, string) error
renameFn func([]string, string, string) error
openFn func([]string, string) (StoreEntry, error)
openFn func([]string, string) (File, error)
statFn func([]string, string) (os.FileInfo, error)
listDirFn func([]string, string) ([]os.DirEntry, error)
children map[string]*DirStore
readdirFn func([]string, string) ([]os.DirEntry, error)
children map[string]*Tree
}
// DirOption configures a DirStore.
type DirOption func(*DirStore)
// Option configures a Tree.
type Option func(*Tree)
// WithReadOnly makes the store reject writes, creates, deletes, and renames.
func WithReadOnly() DirOption { return func(d *DirStore) { d.readOnly = true } }
func WithReadOnly() Option { return func(d *Tree) { d.readOnly = true } }
// WithIndex adds a synthetic read-only "idx" entry whose content is
// generated by calling fn with the store itself.
func WithIndex(fn func(Store) ([]byte, error)) DirOption {
return func(d *DirStore) { d.indexFn = fn }
func WithIndex(fn func(FileTree) ([]byte, error)) Option {
return func(d *Tree) { d.indexFn = fn }
}
// WithResolver overrides how virtual names map to filesystem paths.
func WithResolver(fn func(dirs []string, name string) (string, error)) DirOption {
return func(d *DirStore) { d.resolveFn = fn }
func WithResolver(fn func(dirs []string, name string) (string, error)) Option {
return func(d *Tree) { d.resolveFn = fn }
}
// WithLister overrides how the store lists its entries.
func WithLister(fn func(dirs []string) ([]os.DirEntry, error)) DirOption {
return func(d *DirStore) { d.listFn = fn }
func WithLister(fn func(dirs []string) ([]os.DirEntry, error)) Option {
return func(d *Tree) { d.listFn = fn }
}
// WithCreator overrides file creation logic.
func WithCreator(fn func(dirs []string, name string, perm os.FileMode) error) DirOption {
return func(d *DirStore) { d.createFn = fn }
func WithCreator(fn func(dirs []string, name string, perm os.FileMode) error) Option {
return func(d *Tree) { d.createFn = fn }
}
// WithDeleter overrides file deletion logic.
func WithDeleter(fn func(dirs []string, name string) error) DirOption {
return func(d *DirStore) { d.deleteFn = fn }
func WithDeleter(fn func(dirs []string, name string) error) Option {
return func(d *Tree) { d.deleteFn = fn }
}
// WithRenamer overrides rename logic.
func WithRenamer(fn func(dirs []string, old, new string) error) DirOption {
return func(d *DirStore) { d.renameFn = fn }
func WithRenamer(fn func(dirs []string, old, new string) error) Option {
return func(d *Tree) { d.renameFn = fn }
}
// WithOpener overrides how entries are opened (bypasses filesystem read).
func WithOpener(fn func(dirs []string, name string) (StoreEntry, error)) DirOption {
return func(d *DirStore) { d.openFn = fn }
func WithOpener(fn func(dirs []string, name string) (File, error)) Option {
return func(d *Tree) { d.openFn = fn }
}
// WithStat overrides how entries are stat'd.
func WithStat(fn func(dirs []string, name string) (os.FileInfo, error)) DirOption {
return func(d *DirStore) { d.statFn = fn }
func WithStat(fn func(dirs []string, name string) (os.FileInfo, error)) Option {
return func(d *Tree) { d.statFn = fn }
}
// WithListDir overrides subdirectory listing.
func WithListDir(fn func(dirs []string, name string) ([]os.DirEntry, error)) DirOption {
return func(d *DirStore) { d.listDirFn = fn }
// WithReaddir overrides subdirectory listing.
func WithReaddir(fn func(dirs []string, name string) ([]os.DirEntry, error)) Option {
return func(d *Tree) { d.readdirFn = fn }
}
// NewDirStore creates a DirStore backed by one or more directories.
// NewTree creates a Tree backed by one or more directories.
// The first directory is the write target for unions.
func NewDirStore(dirs []string, perm os.FileMode, opts ...DirOption) *DirStore {
d := &DirStore{dirs: dirs, perm: perm}
func NewTree(dirs []string, perm os.FileMode, opts ...Option) *Tree {
d := &Tree{Tree: dirs, perm: perm}
for _, o := range opts {
o(d)
}
return d
}
// Mount attaches a child DirStore at the given name.
func (d *DirStore) Mount(name string, child *DirStore) {
// Mount attaches a child Tree at the given name.
func (d *Tree) Mount(name string, child *Tree) {
if d.children == nil {
d.children = make(map[string]*DirStore)
d.children = make(map[string]*Tree)
}
d.children[name] = child
}
// Unmount removes a mounted child.
func (d *DirStore) Unmount(name string) {
func (d *Tree) Unmount(name string) {
delete(d.children, name)
}
// Child returns the child mounted at name, or nil.
func (d *DirStore) Child(name string) *DirStore {
func (d *Tree) Child(name string) *Tree {
if d.children == nil {
return nil
}
@ -112,7 +112,7 @@ func (d *DirStore) Child(name string) *DirStore {
// split checks if name routes to a child store. Returns (child, rest, true)
// if a child handles this path, or (nil, "", false) otherwise.
func (d *DirStore) split(name string) (*DirStore, string, bool) {
func (d *Tree) split(name string) (*Tree, string, bool) {
if d.children == nil {
return nil, "", false
}
@ -139,11 +139,11 @@ func indexOf(s string, b byte) int {
return -1
}
func (d *DirStore) resolve(name string) (string, error) {
func (d *Tree) resolve(name string) (string, error) {
if d.resolveFn != nil {
return d.resolveFn(d.dirs, name)
return d.resolveFn(d.Tree, name)
}
for _, dir := range d.dirs {
for _, dir := range d.Tree {
p := filepath.Join(dir, name)
if _, err := os.Stat(p); err == nil {
return p, nil
@ -152,11 +152,11 @@ func (d *DirStore) resolve(name string) (string, error) {
return "", fmt.Errorf("%s: not found", name)
}
func (d *DirStore) writePath(name string) string {
return filepath.Join(d.dirs[0], name)
func (d *Tree) writePath(name string) string {
return filepath.Join(d.Tree[0], name)
}
func (d *DirStore) Stat(name string) (os.FileInfo, error) {
func (d *Tree) Stat(name string) (os.FileInfo, error) {
// Check children first.
if child, rest, ok := d.split(name); ok {
if rest == "" {
@ -169,11 +169,11 @@ func (d *DirStore) Stat(name string) (os.FileInfo, error) {
}
// Custom stat hook.
if d.statFn != nil {
return d.statFn(d.dirs, name)
return d.statFn(d.Tree, name)
}
// Synthetic files: use opener for stat.
if d.openFn != nil {
e, err := d.openFn(d.dirs, name)
e, err := d.openFn(d.Tree, name)
if err != nil {
return nil, err
}
@ -193,9 +193,9 @@ func (d *DirStore) Stat(name string) (os.FileInfo, error) {
return info, nil
}
func (d *DirStore) List() ([]os.DirEntry, error) {
func (d *Tree) List() ([]os.DirEntry, error) {
if d.listFn != nil {
entries, err := d.listFn(d.dirs)
entries, err := d.listFn(d.Tree)
if err != nil {
return nil, err
}
@ -216,7 +216,7 @@ func (d *DirStore) List() ([]os.DirEntry, error) {
result = append(result, FileEntry("idx", 0444))
seen["idx"] = true
}
for _, dir := range d.dirs {
for _, dir := range d.Tree {
entries, err := os.ReadDir(dir)
if err != nil {
continue
@ -236,7 +236,7 @@ func (d *DirStore) List() ([]os.DirEntry, error) {
return result, nil
}
func (d *DirStore) Open(name string) (StoreEntry, error) {
func (d *Tree) Open(name string) (File, error) {
// Delegate to child stores.
if child, rest, ok := d.split(name); ok {
if rest == "" {
@ -248,7 +248,7 @@ func (d *DirStore) Open(name string) (StoreEntry, error) {
return nil, "", fmt.Errorf("blocking read not supported")
}
if name == "idx" && d.indexFn != nil {
return &EntryConfig{
return &FileConfig{
StatFn: func() (os.FileInfo, error) { return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil },
ReadFn: func() ([]byte, error) { return d.indexFn(d) },
WriteFn: func([]byte) error { return fmt.Errorf("idx: read-only") },
@ -256,7 +256,7 @@ func (d *DirStore) Open(name string) (StoreEntry, error) {
}, nil
}
if d.openFn != nil {
return d.openFn(d.dirs, name)
return d.openFn(d.Tree, name)
}
p, err := d.resolve(name)
if err != nil {
@ -276,7 +276,7 @@ func (d *DirStore) Open(name string) (StoreEntry, error) {
}
return os.WriteFile(wp, data, d.perm)
}
return &EntryConfig{
return &FileConfig{
StatFn: func() (os.FileInfo, error) { return os.Stat(p) },
ReadFn: func() ([]byte, error) { return os.ReadFile(p) },
WriteFn: writeFn,
@ -284,7 +284,7 @@ func (d *DirStore) Open(name string) (StoreEntry, error) {
}, nil
}
func (d *DirStore) Create(name string) error {
func (d *Tree) Create(name string) error {
if child, rest, ok := d.split(name); ok && rest != "" {
return child.Create(rest)
}
@ -292,7 +292,7 @@ func (d *DirStore) Create(name string) error {
return fmt.Errorf("read-only store")
}
if d.createFn != nil {
return d.createFn(d.dirs, name, d.perm)
return d.createFn(d.Tree, name, d.perm)
}
p := d.writePath(name)
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
@ -301,7 +301,7 @@ func (d *DirStore) Create(name string) error {
return os.WriteFile(p, nil, d.perm)
}
func (d *DirStore) Delete(name string) error {
func (d *Tree) Delete(name string) error {
if child, rest, ok := d.split(name); ok && rest != "" {
return child.Delete(rest)
}
@ -309,7 +309,7 @@ func (d *DirStore) Delete(name string) error {
return fmt.Errorf("read-only store")
}
if d.deleteFn != nil {
return d.deleteFn(d.dirs, name)
return d.deleteFn(d.Tree, name)
}
p, err := d.resolve(name)
if err != nil {
@ -318,7 +318,7 @@ func (d *DirStore) Delete(name string) error {
return os.Remove(p)
}
func (d *DirStore) Rename(old, new string) error {
func (d *Tree) Rename(old, new string) error {
if child, rest, ok := d.split(old); ok && rest != "" {
return child.Rename(rest, new)
}
@ -326,7 +326,7 @@ func (d *DirStore) Rename(old, new string) error {
return fmt.Errorf("read-only store")
}
if d.renameFn != nil {
return d.renameFn(d.dirs, old, new)
return d.renameFn(d.Tree, old, new)
}
p, err := d.resolve(old)
if err != nil {
@ -335,34 +335,34 @@ func (d *DirStore) Rename(old, new string) error {
return os.Rename(p, filepath.Join(filepath.Dir(p), new))
}
// ListDir lists entries in a subdirectory.
func (d *DirStore) ListDir(rel string) ([]os.DirEntry, error) {
// Readdir lists entries in a subdirectory.
func (d *Tree) Readdir(rel string) ([]os.DirEntry, error) {
// Check if rel routes to a child.
if child, rest, ok := d.split(rel); ok {
if rest == "" {
return child.List()
}
return child.ListDir(rest)
return child.Readdir(rest)
}
if d.listDirFn != nil {
return d.listDirFn(d.dirs, rel)
if d.readdirFn != nil {
return d.readdirFn(d.Tree, rel)
}
if len(d.dirs) == 0 {
if len(d.Tree) == 0 {
return nil, fmt.Errorf("%s: not found", rel)
}
return os.ReadDir(filepath.Join(d.dirs[0], rel))
return os.ReadDir(filepath.Join(d.Tree[0], rel))
}
// MkdirAll creates a directory relative to the first backing dir.
func (d *DirStore) MkdirAll(rel string) error {
func (d *Tree) MkdirAll(rel string) error {
if child, rest, ok := d.split(rel); ok && rest != "" {
return child.MkdirAll(rest)
}
if d.readOnly {
return fmt.Errorf("read-only store")
}
if len(d.dirs) == 0 {
if len(d.Tree) == 0 {
return fmt.Errorf("no backing directory")
}
return os.MkdirAll(filepath.Join(d.dirs[0], rel), 0755)
}
return os.MkdirAll(filepath.Join(d.Tree[0], rel), 0755)
}

View File

@ -14,7 +14,7 @@ import (
"ollie/pkg/backend"
olog "ollie/pkg/log"
"olliesrv/session"
"olliesrv/store"
"olliesrv/fs"
)
// --- stub agent.Core ---
@ -137,7 +137,7 @@ func newTestSessionManagerWithCore(t *testing.T) *session.Manager {
})
}
func testStoreRead(t *testing.T, s store.Store, name string) []byte {
func testStoreRead(t *testing.T, s fs.FileTree, name string) []byte {
t.Helper()
e, err := s.Open(name)
if err != nil { t.Fatalf("Open(%s): %v", name, err) }
@ -146,7 +146,7 @@ func testStoreRead(t *testing.T, s store.Store, name string) []byte {
return data
}
func testStoreWrite(t *testing.T, s store.Store, name string, data []byte) {
func testStoreWrite(t *testing.T, s fs.FileTree, name string, data []byte) {
t.Helper()
e, err := s.Open(name)
if err != nil { t.Fatalf("Open(%s): %v", name, err) }
@ -162,7 +162,7 @@ func newTestSessionFileStore(t *testing.T, sess *session.Session) (*session.File
t.Helper()
sink := testSink()
core := sess.Core.(*stubCore)
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {},
func(id string) error { return nil },
func(data []byte) error { return nil },
@ -173,7 +173,7 @@ func newTestSessionFileStore(t *testing.T, sess *session.Session) (*session.File
func newTestSessionFileStoreWith(t *testing.T, sess *session.Session, kill func(), rename func(string) error, save func([]byte) error) *session.FileStore {
t.Helper()
sink := testSink()
return session.NewFileStore(sess, sink.NewLogger("test"), kill, rename, save)
return session.NewSynth(sess, sink.NewLogger("test"), kill, rename, save)
}
// ===== session.Session =====
@ -395,7 +395,7 @@ func TestSessionFileStoreReadableContract(t *testing.T) {
sess := testSession("s1")
defer sess.Cancel()
sink := testSink()
_ = session.NewFileStore(sess, sink.NewLogger("test"),
_ = session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
}
@ -403,7 +403,7 @@ func TestSessionFileStoreList(t *testing.T) {
sess := testSession("s1")
defer sess.Cancel()
sink := testSink()
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
entries, err := sf.List()
@ -420,7 +420,7 @@ func TestSessionFileStoreStatChat(t *testing.T) {
defer sess.Cancel()
sess.AppendLog([]byte("hello"))
sink := testSink()
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
fi, err := sf.Stat("chat")
@ -437,7 +437,7 @@ func TestSessionFileStoreGetChat(t *testing.T) {
defer sess.Cancel()
sess.AppendLog([]byte("hello"))
sink := testSink()
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
data := testStoreRead(t, sf, "chat")
@ -450,7 +450,7 @@ func TestSessionFileStoreGetContent(t *testing.T) {
sess := testSession("s1")
defer sess.Cancel()
sink := testSink()
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
for _, name := range []string{"cfg", "offset", "usage", "ctxsz", "models", "systemprompt"} {
@ -464,7 +464,7 @@ func TestSessionFileStorePutCwd(t *testing.T) {
sess := testSession("s1")
defer sess.Cancel()
sink := testSink()
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
testStoreWrite(t, sf, "cfg", []byte("cwd=/new/path"))
@ -478,7 +478,7 @@ func TestSessionFileStorePutEmpty(t *testing.T) {
sess := testSession("s1")
defer sess.Cancel()
sink := testSink()
sf := session.NewFileStore(sess, sink.NewLogger("test"),
sf := session.NewSynth(sess, sink.NewLogger("test"),
func() {}, func(string) error { return nil }, func([]byte) error { return nil })
// Empty write is a no-op
@ -985,7 +985,7 @@ func TestFormatEvent(t *testing.T) {
// ===== SyntheticFileInfo / SyntheticEntry / FileEntry / DirEntry =====
func TestSyntheticFileInfo(t *testing.T) {
fi := &store.SyntheticFileInfo{Name_: "f", Mode_: 0644, Size_: 42, IsDir_: false}
fi := &fs.SyntheticFileInfo{Name_: "f", Mode_: 0644, Size_: 42, IsDir_: false}
if fi.Name() != "f" || fi.Size() != 42 || fi.Mode() != 0644 || fi.IsDir() || fi.Sys() != nil {
t.Error("SyntheticFileInfo field mismatch")
}
@ -995,11 +995,11 @@ func TestSyntheticFileInfo(t *testing.T) {
}
func TestFileEntryDirEntry(t *testing.T) {
fe := store.FileEntry("f", 0644)
fe := fs.FileEntry("f", 0644)
if fe.Name() != "f" || fe.IsDir() || fe.Type() != 0 {
t.Error("FileEntry mismatch")
}
de := store.DirEntry("d", 0755)
de := fs.DirEntry("d", 0755)
if de.Name() != "d" || !de.IsDir() || de.Type() != os.ModeDir {
t.Error("DirEntry mismatch")
}

View File

@ -20,17 +20,17 @@ import (
"ollie/pkg/paths"
"ollie/pkg/tools/execute"
"olliesrv/session"
"olliesrv/store"
"olliesrv/fs"
"9fans.net/go/plan9"
)
// Re-export store types for use in server.go.
type (
StoreEntry = store.StoreEntry
Store = store.Store
File = fs.File
FileTree = fs.FileTree
syntheticFileInfo = store.SyntheticFileInfo
syntheticFileInfo = fs.SyntheticFileInfo
)
const (
@ -64,17 +64,17 @@ type Server struct {
log *olog.Logger
sink *olog.Sink
agentsDir string // kept for session creation wiring
agentStore Store
promptStore Store
memStore Store
toolStore *store.DirStore
utilStore Store
pluginStore Store
skillStore Store
agentStore FileTree
promptStore FileTree
memStore FileTree
toolStore *fs.Tree
utilStore FileTree
pluginStore FileTree
skillStore FileTree
sessionMgr *session.Manager
transcriptStore Store
tmpStore Store
rootStore *store.DirStore
transcriptStore FileTree
tmpStore FileTree
rootStore *fs.Tree
strict bool
yolo bool
groups map[string]map[string]bool // group → set of members
@ -101,16 +101,16 @@ func New(sink *olog.Sink, opts ...Option) *Server {
log: sink.Logger("9p", olog.LevelDebug),
sink: sink,
agentsDir: agentsDir,
agentStore: store.NewDirStore(agentsDirs, 0644),
promptStore: store.NewDirStore(agent.PromptsDirs(), 0444, store.WithReadOnly()),
memStore: store.NewDirStore([]string{memDir}, 0644),
toolStore: store.NewDirStore([]string{execute.ToolsPath()}, 0755, store.WithIndex(store.ToolIndex)),
utilStore: store.NewDirStore([]string{paths.CfgDir() + "/scripts/u"}, 0555, store.WithReadOnly()),
pluginStore: store.NewDirStore([]string{execute.PluginsPath()}, 0555, store.WithReadOnly()),
skillStore: store.NewSkillStore(),
transcriptStore: store.NewDirStore([]string{transcriptDir}, 0444, store.WithReadOnly()),
tmpStore: store.NewDirStore([]string{tmpDir}, 0600),
rootStore: store.NewRootStore(),
agentStore: fs.NewTree(agentsDirs, 0644),
promptStore: fs.NewTree(agent.PromptsDirs(), 0444, fs.WithReadOnly()),
memStore: fs.NewTree([]string{memDir}, 0644),
toolStore: fs.NewTree([]string{execute.ToolsPath()}, 0755, fs.WithIndex(fs.ToolIndex)),
utilStore: fs.NewTree([]string{paths.CfgDir() + "/scripts/u"}, 0555, fs.WithReadOnly()),
pluginStore: fs.NewTree([]string{execute.PluginsPath()}, 0555, fs.WithReadOnly()),
skillStore: fs.NewSkillStore(),
transcriptStore: fs.NewTree([]string{transcriptDir}, 0444, fs.WithReadOnly()),
tmpStore: fs.NewTree([]string{tmpDir}, 0600),
rootStore: fs.NewRootStore(),
groups: make(map[string]map[string]bool),
}
for _, o := range opts {
@ -151,29 +151,29 @@ func New(sink *olog.Sink, opts ...Option) *Server {
return s
}
// storeRoute maps a path prefix to its backing store.
// storeRoute maps a path prefix to its backing fs.
type storeRoute struct {
prefix string
store func() Store
store func() FileTree
}
// storeRoutes returns the route table for generic store-backed paths.
func (s *Server) storeRoutes() []storeRoute {
return []storeRoute{
{"/a/", func() Store { return s.agentStore }},
{"/p/", func() Store { return s.promptStore }},
{"/m/", func() Store { return s.memStore }},
{"/s/", func() Store { return s.sessionMgr.Store() }},
{"/sk/", func() Store { return s.skillStore }},
{"/u/", func() Store { return s.utilStore }},
{"/x/", func() Store { return s.pluginStore }},
{"/tmp/", func() Store { return s.tmpStore }},
{"/tr/", func() Store { return s.transcriptStore }},
{"/a/", func() FileTree { return s.agentStore }},
{"/p/", func() FileTree { return s.promptStore }},
{"/m/", func() FileTree { return s.memStore }},
{"/s/", func() FileTree { return s.sessionMgr.Store() }},
{"/sk/", func() FileTree { return s.skillStore }},
{"/u/", func() FileTree { return s.utilStore }},
{"/x/", func() FileTree { return s.pluginStore }},
{"/tmp/", func() FileTree { return s.tmpStore }},
{"/tr/", func() FileTree { return s.transcriptStore }},
}
}
// routeStore returns the store and entry name for a path, or nil if no route matches.
func (s *Server) routeStore(path string) (Store, string) {
func (s *Server) routeStore(path string) (FileTree, string) {
for _, r := range s.storeRoutes() {
if strings.HasPrefix(path, r.prefix) {
return r.store(), strings.TrimPrefix(path, r.prefix)
@ -189,8 +189,8 @@ func (s *Server) routeStore(path string) (Store, string) {
return nil, ""
}
// openEntry opens a StoreEntry for the given path.
func (s *Server) openEntry(path string) (StoreEntry, error) {
// openEntry opens a File for the given path.
func (s *Server) openEntry(path string) (File, error) {
st, name := s.routeStore(path)
if st == nil {
return nil, fmt.Errorf("%s: not found", path)
@ -199,7 +199,7 @@ func (s *Server) openEntry(path string) (StoreEntry, error) {
}
// routeDir returns the store for a directory path (e.g. "/a" → agentStore).
func (s *Server) routeDir(path string) Store {
func (s *Server) routeDir(path string) FileTree {
for _, r := range s.storeRoutes() {
if path+"/" == r.prefix {
return r.store()
@ -328,7 +328,7 @@ func defaultTmpDir() string {
// storeRead opens an entry in a store and reads it.
func storeRead(s store.Store, name string) ([]byte, error) {
func storeRead(s fs.FileTree, name string) ([]byte, error) {
e, err := s.Open(name)
if err != nil {
return nil, err
@ -337,7 +337,7 @@ func storeRead(s store.Store, name string) ([]byte, error) {
}
// storeWrite opens an entry in a store and writes to it.
func storeWrite(s store.Store, name string, data []byte) error {
func storeWrite(s fs.FileTree, name string, data []byte) error {
e, err := s.Open(name)
if err != nil {
return err
@ -665,7 +665,7 @@ func (s *Server) create(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
s.log.Debug("Tcreate parent=%q name=%q", f.path, fc.Name)
// File create: route to the parent directory's store.
// File create: route to the parent directory's fs.
if st := s.routeDir(f.path); st != nil {
if err := st.Create(fc.Name); err != nil {
return errFcall(fc, err.Error())
@ -975,10 +975,10 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
} else if st, name := s.routeStore(path); st != nil {
// Subdirectory within a store (e.g. /s/mysession, /s/mysession/t).
type dirLister interface {
ListDir(string) ([]os.DirEntry, error)
Readdir(string) ([]os.DirEntry, error)
}
if dl, ok := st.(dirLister); ok {
entries, _ := dl.ListDir(name)
entries, _ := dl.Readdir(name)
for _, e := range entries {
fpath := path + "/" + e.Name()
d := s.makeStat(fpath)
@ -1135,7 +1135,7 @@ func (s *Server) makeStat(path string) plan9.Dir {
}
}
// For memory and plan files, report real size and timestamps from the store.
// For memory and plan files, report real size and timestamps from the fs.
if strings.HasPrefix(path, "/m/") {
if info, err := s.memStore.Stat(base); err == nil {
dir.Length = uint64(info.Size())

View File

@ -16,7 +16,7 @@ import (
"ollie/pkg/paths"
"ollie/pkg/tools"
"ollie/pkg/tools/execute"
"olliesrv/store"
"olliesrv/fs"
)
// Session holds all state for one agent session.
@ -113,7 +113,7 @@ type ManagerConfig struct {
OnRename func(oldID, newID string)
SaveTranscript func([]byte) error
// ToolStore provides access to the shared tool directory.
ToolStore store.Store
ToolStore fs.FileTree
// NewCore, if non-nil, replaces the default backend.New + agent.NewAgentCore
// path. It receives the session ID, agent name, and cwd, and returns a Core.
NewCore func(sessionID, agentName, cwd string) (agent.Core, error)
@ -123,17 +123,17 @@ type ManagerConfig struct {
Yolo bool
}
// Manager manages session lifecycle and exposes sessions as a DirStore.
// Manager manages session lifecycle and exposes sessions as a Tree.
type Manager struct {
store *store.DirStore
store *fs.Tree
cfg ManagerConfig
mu sync.RWMutex
sessions map[string]*Session
nextUID atomic.Uint32 // incrementing principal counter
}
// Store returns the DirStore view of the session namespace.
func (s *Manager) Store() *store.DirStore { return s.store }
// Store returns the Tree view of the session namespace.
func (s *Manager) Store() *fs.Tree { return s.store }
// nextUname generates the next uname atomically.
func (s *Manager) nextUname() string {
@ -152,19 +152,19 @@ func NewManager(cfg ManagerConfig) *Manager {
sessions: make(map[string]*Session),
}
ss.nextUID.Store(9999)
ss.store = store.NewDirStore(nil, 0,
store.WithStat(func(_ []string, name string) (os.FileInfo, error) { return ss.stat(name) }),
store.WithOpener(func(_ []string, name string) (store.StoreEntry, error) { return ss.openEntry(name) }),
store.WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
store.WithListDir(func(_ []string, name string) ([]os.DirEntry, error) { return ss.ListDir(name) }),
store.WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
store.WithCreator(func(_ []string, name string, _ os.FileMode) error { return ss.create(name) }),
store.WithRenamer(func(_ []string, old, new string) error { return ss.renameSession(old, new) }),
ss.store = fs.NewTree(nil, 0,
fs.WithStat(func(_ []string, name string) (os.FileInfo, error) { return ss.stat(name) }),
fs.WithOpener(func(_ []string, name string) (fs.File, error) { return ss.openEntry(name) }),
fs.WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
fs.WithReaddir(func(_ []string, name string) ([]os.DirEntry, error) { return ss.Readdir(name) }),
fs.WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
fs.WithCreator(func(_ []string, name string, _ os.FileMode) error { return ss.create(name) }),
fs.WithRenamer(func(_ []string, old, new string) error { return ss.renameSession(old, new) }),
)
return ss
}
// AddSession inserts a pre-built session into the store.
// AddSession inserts a pre-built session into the fs.
func (s *Manager) AddSession(sess *Session) {
s.mu.Lock()
s.sessions[sess.RunnableID()] = sess
@ -174,18 +174,18 @@ func (s *Manager) AddSession(sess *Session) {
func (s *Manager) list() ([]os.DirEntry, error) {
entries := make([]os.DirEntry, 0, len(sessionStoreOrder))
for _, name := range sessionStoreOrder {
entries = append(entries, store.FileEntry(name, sessionStoreFiles[name]))
entries = append(entries, fs.FileEntry(name, sessionStoreFiles[name]))
}
s.mu.RLock()
for id := range s.sessions {
entries = append(entries, store.DirEntry(id, 0555))
entries = append(entries, fs.DirEntry(id, 0555))
}
s.mu.RUnlock()
return entries, nil
}
// ListDir lists entries in a subdirectory (e.g. "{id}", "{id}/t", "{id}/t/{subdir}").
func (s *Manager) ListDir(name string) ([]os.DirEntry, error) {
// Readdir lists entries in a subdirectory (e.g. "{id}", "{id}/t", "{id}/t/{subdir}").
func (s *Manager) Readdir(name string) ([]os.DirEntry, error) {
parts := strings.SplitN(name, "/", 3)
sessID := parts[0]
sess := s.Session(sessID)
@ -202,7 +202,7 @@ func (s *Manager) ListDir(name string) ([]os.DirEntry, error) {
if err != nil {
return nil, err
}
entries = append(entries, store.DirEntry("t", 0500))
entries = append(entries, fs.DirEntry("t", 0500))
return entries, nil
}
// {id}/t — list tools (filtered by allowTools)
@ -210,12 +210,12 @@ func (s *Manager) ListDir(name string) ([]os.DirEntry, error) {
if len(parts) == 2 {
return s.cfg.ToolStore.List()
}
// {id}/t/{subdir} — delegate to ToolStore.ListDir if available
// {id}/t/{subdir} — delegate to ToolStore.Readdir if available
type listDirer interface {
ListDir(string) ([]os.DirEntry, error)
Readdir(string) ([]os.DirEntry, error)
}
if ld, ok := s.cfg.ToolStore.(listDirer); ok {
return ld.ListDir(parts[2])
return ld.Readdir(parts[2])
}
}
return nil, fmt.Errorf("%s: not a directory", name)
@ -224,7 +224,7 @@ func (s *Manager) ListDir(name string) ([]os.DirEntry, error) {
func (s *Manager) stat(name string) (os.FileInfo, error) {
// Top-level fixed files (new, idx, sh, etc.)
if mode, ok := sessionStoreFiles[name]; ok {
return &store.SyntheticFileInfo{Name_: name, Mode_: mode}, nil
return &fs.SyntheticFileInfo{Name_: name, Mode_: mode}, nil
}
parts := strings.SplitN(name, "/", 3)
sessID := parts[0]
@ -236,12 +236,12 @@ func (s *Manager) stat(name string) (os.FileInfo, error) {
}
// Session directory: {id}
if len(parts) == 1 {
return &store.SyntheticFileInfo{Name_: sessID, Mode_: 0555, IsDir_: true}, nil
return &fs.SyntheticFileInfo{Name_: sessID, Mode_: 0555, IsDir_: true}, nil
}
// Tools directory: {id}/t
if parts[1] == "t" {
if len(parts) == 2 {
return &store.SyntheticFileInfo{Name_: "t", Mode_: 0500, IsDir_: true}, nil
return &fs.SyntheticFileInfo{Name_: "t", Mode_: 0500, IsDir_: true}, nil
}
// Tool file: {id}/t/{rel}
if s.cfg.ToolStore != nil {
@ -257,7 +257,7 @@ func (s *Manager) stat(name string) (os.FileInfo, error) {
return sfs.Stat(parts[1])
}
func (s *Manager) openEntry(name string) (store.StoreEntry, error) {
func (s *Manager) openEntry(name string) (fs.File, error) {
notBlocking := func(context.Context, string) ([]byte, string, error) {
return nil, "", fmt.Errorf("blocking read not supported")
}
@ -265,8 +265,8 @@ func (s *Manager) openEntry(name string) (store.StoreEntry, error) {
// Top-level fixed files.
switch name {
case "new":
return &store.EntryConfig{
StatFn: func() (os.FileInfo, error) { return &store.SyntheticFileInfo{Name_: "new", Mode_: 0666}, nil },
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: "new", Mode_: 0666}, nil },
ReadFn: func() ([]byte, error) {
return []byte("name=\ncwd=\nbackend=\nmodel=\nagent=\nmaxTokens=\nmaxCompletionTokens=\ntemperature=\ntopP=\ntopK=\nminP=\ntopA=\nfrequencyPenalty=\npresencePenalty=\nrepetitionPenalty=\nreasoning=\nreasoningEffort=\nincludeReasoning=\nresponseFormat=\nstop=\nverbosity=\n"), nil
},
@ -276,16 +276,16 @@ func (s *Manager) openEntry(name string) (store.StoreEntry, error) {
BlockingReadFn: notBlocking,
}, nil
case "idx":
return &store.EntryConfig{
StatFn: func() (os.FileInfo, error) { return &store.SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil },
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil },
ReadFn: func() ([]byte, error) { return s.index(), nil },
WriteFn: func([]byte) error { return fmt.Errorf("idx: read-only") },
BlockingReadFn: notBlocking,
}, nil
}
if _, ok := sessionStoreFiles[name]; ok {
return &store.EntryConfig{
StatFn: func() (os.FileInfo, error) { return &store.SyntheticFileInfo{Name_: name, Mode_: sessionStoreFiles[name]}, nil },
return &fs.FileConfig{
StatFn: func() (os.FileInfo, error) { return &fs.SyntheticFileInfo{Name_: name, Mode_: sessionStoreFiles[name]}, nil },
ReadFn: func() ([]byte, error) {
return s.cfg.ReadFile(paths.CfgDir() + "/scripts/s/" + name)
},
@ -332,12 +332,12 @@ func (s *Manager) openEntry(name string) (store.StoreEntry, error) {
}
func (s *Manager) openFilteredToolIdx(allowed map[string]bool) (store.StoreEntry, error) {
func (s *Manager) openFilteredToolIdx(allowed map[string]bool) (fs.File, error) {
base, err := s.cfg.ToolStore.Open("idx")
if err != nil {
return nil, err
}
return &store.EntryConfig{
return &fs.FileConfig{
StatFn: base.Stat,
ReadFn: func() ([]byte, error) {
data, err := base.Read()
@ -375,7 +375,7 @@ func (s *Manager) create(name string) error {
return fmt.Errorf("create not supported: %s", name)
}
// MkdirAll creates a directory within the session's tool store.
// MkdirAll creates a directory within the session's tool fs.
func (s *Manager) MkdirAll(name string) error {
parts := strings.SplitN(name, "/", 3)
if len(parts) == 3 && parts[1] == "t" {
@ -433,17 +433,17 @@ func (s *Manager) SessionByUname(uname string) *Session {
return nil
}
// OpenStore returns a DirStore for the given session ID.
func (s *Manager) OpenStore(id string) (*store.DirStore, error) {
// OpenStore returns a Tree for the given session ID.
func (s *Manager) OpenStore(id string) (*fs.Tree, error) {
if sess := s.Session(id); sess != nil {
return s.openStore(sess)
}
return nil, fmt.Errorf("session not found: %s", id)
}
func (s *Manager) openStore(sess *Session) (*store.DirStore, error) {
func (s *Manager) openStore(sess *Session) (*fs.Tree, error) {
id := sess.RunnableID()
return NewFileStore(
return NewSynth(
sess,
s.cfg.Log,
func() { s.KillSession(id) },

View File

@ -11,7 +11,7 @@ import (
"ollie/pkg/agent"
"ollie/pkg/backend"
olog "ollie/pkg/log"
"olliesrv/store"
"olliesrv/fs"
)
// FileList defines the fixed set of files in a session directory.
@ -42,18 +42,18 @@ var FileList = []struct {
{"context", 0444, false, false},
}
// FileStore is a DirStore for a session directory.
type FileStore = store.DirStore
// FileStore is a Tree for a session directory.
type FileStore = fs.Tree
func NewFileStore(sess *Session, log *olog.Logger, kill func(), rename func(newID string) error, saveTranscript func([]byte) error) *FileStore {
func NewSynth(sess *Session, log *olog.Logger, kill func(), rename func(newID string) error, saveTranscript func([]byte) error) *FileStore {
h := &sessionHelper{sess: sess, log: log, kill: kill, rename: rename, saveTranscript: saveTranscript}
specs := make([]store.FileSpec, len(FileList))
specs := make([]fs.FileSpec, len(FileList))
for i, f := range FileList {
specs[i] = h.fileSpec(f.Name, f.Mode)
specs[i].OneShot = f.OneShot
specs[i].Async = f.Async
}
return store.NewFileStore(specs)
return fs.NewSynth(specs)
}
// sessionHelper holds the dependencies needed to build session FileSpecs.
@ -65,8 +65,8 @@ type sessionHelper struct {
saveTranscript func([]byte) error
}
func (h *sessionHelper) fileSpec(name string, mode os.FileMode) store.FileSpec {
fs := store.FileSpec{Name: name, Mode: mode}
func (h *sessionHelper) fileSpec(name string, mode os.FileMode) fs.FileSpec {
fs := fs.FileSpec{Name: name, Mode: mode}
// Read
switch name {