diff --git a/store/store.go b/fs/fs.go similarity index 75% rename from store/store.go rename to fs/fs.go index f5aa8df..6bdf4f5 100644 --- a/store/store.go +++ b/fs/fs.go @@ -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 { diff --git a/store/store_test.go b/fs/fs_test.go similarity index 89% rename from store/store_test.go rename to fs/fs_test.go index 8710d36..5de6a73 100644 --- a/store/store_test.go +++ b/fs/fs_test.go @@ -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") }, }) diff --git a/store/memory.go b/fs/memory.go similarity index 92% rename from store/memory.go rename to fs/memory.go index 73d7024..21e88ac 100644 --- a/store/memory.go +++ b/fs/memory.go @@ -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() diff --git a/store/root.go b/fs/root.go similarity index 83% rename from store/root.go rename to fs/root.go index cdf99c1..ae3fa51 100644 --- a/store/root.go +++ b/fs/root.go @@ -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, diff --git a/store/skill.go b/fs/skill.go similarity index 93% rename from store/skill.go rename to fs/skill.go index 57c6807..8994a01 100644 --- a/store/skill.go +++ b/fs/skill.go @@ -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) diff --git a/store/filestore.go b/fs/synth.go similarity index 86% rename from store/filestore.go rename to fs/synth.go index af70b47..f0317af 100644 --- a/store/filestore.go +++ b/fs/synth.go @@ -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 { diff --git a/store/tool.go b/fs/tool.go similarity index 95% rename from store/tool.go rename to fs/tool.go index a90e9e1..05a9d7a 100644 --- a/store/tool.go +++ b/fs/tool.go @@ -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 diff --git a/store/dirstore.go b/fs/tree.go similarity index 68% rename from store/dirstore.go rename to fs/tree.go index 63d3d89..323a474 100644 --- a/store/dirstore.go +++ b/fs/tree.go @@ -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) +} \ No newline at end of file diff --git a/main_test.go b/main_test.go index 4a40782..74d9661 100644 --- a/main_test.go +++ b/main_test.go @@ -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") } diff --git a/server/server.go b/server/server.go index 5dbbd7a..f8ec12c 100644 --- a/server/server.go +++ b/server/server.go @@ -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()) diff --git a/session/session.go b/session/session.go index de7adf9..521dced 100644 --- a/session/session.go +++ b/session/session.go @@ -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) }, diff --git a/session/sessionfile.go b/session/sessionfile.go index ba2c12e..454f608 100644 --- a/session/sessionfile.go +++ b/session/sessionfile.go @@ -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 {