- Introduce new Tai SDK tests in the GitHub workflows, requiring a Tai container with Docker socket mount for execution. - Update the Makefile to include a dedicated target for running Tai SDK tests, enhancing test coverage for the Tai integration. - Modify the Go module dependencies to include the pierrec/lz4 package, ensuring compatibility with the new tests. - Adjust test folder selection logic in the Makefile to exclude additional directories, streamlining the testing process.
195 lines
5.2 KiB
Go
195 lines
5.2 KiB
Go
package workspace
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/yaoapp/yao/tai/volume"
|
|
)
|
|
|
|
// FS extends Go's fs.FS with write operations.
|
|
// Backed by volume.Volume — works for both Remote and Local transparently.
|
|
type FS interface {
|
|
fs.FS
|
|
fs.StatFS
|
|
fs.ReadFileFS
|
|
fs.ReadDirFS
|
|
io.Closer
|
|
|
|
WriteFile(name string, data []byte, perm os.FileMode) error
|
|
Remove(name string) error
|
|
RemoveAll(name string) error
|
|
Rename(oldname, newname string) error
|
|
MkdirAll(name string, perm os.FileMode) error
|
|
}
|
|
|
|
// New creates an FS backed by the given Volume for the specified session.
|
|
func New(vol volume.Volume, sessionID string) FS {
|
|
return &workspaceFS{vol: vol, session: sessionID}
|
|
}
|
|
|
|
type workspaceFS struct {
|
|
vol volume.Volume
|
|
session string
|
|
}
|
|
|
|
func (w *workspaceFS) Open(name string) (fs.File, error) {
|
|
if !fs.ValidPath(name) {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
|
}
|
|
ctx := context.Background()
|
|
info, err := w.vol.Stat(ctx, w.session, name)
|
|
if err != nil {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
if info.IsDir {
|
|
return &dirFile{w: w, name: name, info: info}, nil
|
|
}
|
|
data, _, err := w.vol.ReadFile(ctx, w.session, name)
|
|
if err != nil {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
return &memFile{name: name, info: info, data: data}, nil
|
|
}
|
|
|
|
func (w *workspaceFS) Stat(name string) (fs.FileInfo, error) {
|
|
if !fs.ValidPath(name) {
|
|
return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid}
|
|
}
|
|
info, err := w.vol.Stat(context.Background(), w.session, name)
|
|
if err != nil {
|
|
return nil, &fs.PathError{Op: "stat", Path: name, Err: err}
|
|
}
|
|
return toFSInfo(name, info), nil
|
|
}
|
|
|
|
func (w *workspaceFS) ReadFile(name string) ([]byte, error) {
|
|
if !fs.ValidPath(name) {
|
|
return nil, &fs.PathError{Op: "read", Path: name, Err: fs.ErrInvalid}
|
|
}
|
|
data, _, err := w.vol.ReadFile(context.Background(), w.session, name)
|
|
if err != nil {
|
|
return nil, &fs.PathError{Op: "read", Path: name, Err: err}
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (w *workspaceFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
|
if !fs.ValidPath(name) {
|
|
return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid}
|
|
}
|
|
entries, err := w.vol.ListDir(context.Background(), w.session, name)
|
|
if err != nil {
|
|
return nil, &fs.PathError{Op: "readdir", Path: name, Err: err}
|
|
}
|
|
result := make([]fs.DirEntry, 0, len(entries))
|
|
for i := range entries {
|
|
result = append(result, &dirEntry{info: &entries[i]})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (w *workspaceFS) WriteFile(name string, data []byte, perm os.FileMode) error {
|
|
return w.vol.WriteFile(context.Background(), w.session, name, data, perm)
|
|
}
|
|
|
|
func (w *workspaceFS) Remove(name string) error {
|
|
return w.vol.Remove(context.Background(), w.session, name, false)
|
|
}
|
|
|
|
func (w *workspaceFS) RemoveAll(name string) error {
|
|
return w.vol.Remove(context.Background(), w.session, name, true)
|
|
}
|
|
|
|
func (w *workspaceFS) Rename(oldname, newname string) error {
|
|
return w.vol.Rename(context.Background(), w.session, oldname, newname)
|
|
}
|
|
|
|
func (w *workspaceFS) MkdirAll(name string, _ os.FileMode) error {
|
|
return w.vol.MkdirAll(context.Background(), w.session, name)
|
|
}
|
|
|
|
func (w *workspaceFS) Close() error { return nil }
|
|
|
|
// --- fs.FileInfo adapter ---
|
|
|
|
type fileInfoAdapter struct {
|
|
name string
|
|
size int64
|
|
mode fs.FileMode
|
|
mtime time.Time
|
|
isDir bool
|
|
}
|
|
|
|
func toFSInfo(name string, vi *volume.FileInfo) *fileInfoAdapter {
|
|
base := name
|
|
if idx := strings.LastIndex(name, "/"); idx >= 0 {
|
|
base = name[idx+1:]
|
|
}
|
|
if base == "" {
|
|
base = "."
|
|
}
|
|
return &fileInfoAdapter{
|
|
name: base,
|
|
size: vi.Size,
|
|
mode: vi.Mode,
|
|
mtime: vi.Mtime,
|
|
isDir: vi.IsDir,
|
|
}
|
|
}
|
|
|
|
func (f *fileInfoAdapter) Name() string { return f.name }
|
|
func (f *fileInfoAdapter) Size() int64 { return f.size }
|
|
func (f *fileInfoAdapter) Mode() fs.FileMode { return f.mode }
|
|
func (f *fileInfoAdapter) ModTime() time.Time { return f.mtime }
|
|
func (f *fileInfoAdapter) IsDir() bool { return f.isDir }
|
|
func (f *fileInfoAdapter) Sys() any { return nil }
|
|
|
|
// --- fs.DirEntry adapter ---
|
|
|
|
type dirEntry struct {
|
|
info *volume.FileInfo
|
|
}
|
|
|
|
func (d *dirEntry) Name() string { return d.info.Path }
|
|
func (d *dirEntry) IsDir() bool { return d.info.IsDir }
|
|
func (d *dirEntry) Type() fs.FileMode { return d.info.Mode.Type() }
|
|
func (d *dirEntry) Info() (fs.FileInfo, error) { return toFSInfo(d.info.Path, d.info), nil }
|
|
|
|
// --- in-memory file (for Open on regular files) ---
|
|
|
|
type memFile struct {
|
|
name string
|
|
info *volume.FileInfo
|
|
data []byte
|
|
offset int
|
|
}
|
|
|
|
func (f *memFile) Stat() (fs.FileInfo, error) { return toFSInfo(f.name, f.info), nil }
|
|
func (f *memFile) Read(b []byte) (int, error) {
|
|
if f.offset >= len(f.data) {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(b, f.data[f.offset:])
|
|
f.offset += n
|
|
return n, nil
|
|
}
|
|
func (f *memFile) Close() error { return nil }
|
|
|
|
// --- directory file (for Open on directories) ---
|
|
|
|
type dirFile struct {
|
|
w *workspaceFS
|
|
name string
|
|
info *volume.FileInfo
|
|
}
|
|
|
|
func (d *dirFile) Stat() (fs.FileInfo, error) { return toFSInfo(d.name, d.info), nil }
|
|
func (d *dirFile) Read([]byte) (int, error) {
|
|
return 0, &fs.PathError{Op: "read", Path: d.name, Err: fs.ErrInvalid}
|
|
}
|
|
func (d *dirFile) Close() error { return nil }
|