Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions internal/filer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func (f filerTest) assertContents(ctx context.Context, name string, contents str
return
}

defer reader.Close()

var body bytes.Buffer
_, err = io.Copy(&body, reader)
if !assert.NoError(f, err) {
Expand Down Expand Up @@ -309,3 +311,20 @@ func TestAccFilerDbfsReadDir(t *testing.T) {
ctx, f := setupFilerDbfsTest(t)
runFilerReadDirTest(t, ctx, f)
}

func setupFilerLocalTest(t *testing.T) (context.Context, filer.Filer) {
ctx := context.Background()
f, err := filer.NewLocalClient(t.TempDir())
require.NoError(t, err)
return ctx, f
}

func TestAccFilerLocalReadWrite(t *testing.T) {
ctx, f := setupFilerLocalTest(t)
runFilerReadWriteTest(t, ctx, f)
}

func TestAccFilerLocalReadDir(t *testing.T) {
ctx, f := setupFilerLocalTest(t)
runFilerReadDirTest(t, ctx, f)
}
10 changes: 8 additions & 2 deletions libs/filer/dbfs_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (w *DbfsClient) Write(ctx context.Context, name string, reader io.Reader, m
return err
}

func (w *DbfsClient) Read(ctx context.Context, name string) (io.Reader, error) {
func (w *DbfsClient) Read(ctx context.Context, name string) (io.ReadCloser, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
Expand All @@ -159,7 +159,13 @@ func (w *DbfsClient) Read(ctx context.Context, name string) (io.Reader, error) {
return nil, NotAFile{absPath}
}

return w.workspaceClient.Dbfs.Open(ctx, absPath, files.FileModeRead)
handle, err := w.workspaceClient.Dbfs.Open(ctx, absPath, files.FileModeRead)
if err != nil {
return nil, err
}

// A DBFS handle open for reading does not need to be closed.
return io.NopCloser(handle), nil
}

func (w *DbfsClient) Delete(ctx context.Context, name string, mode ...DeleteMode) error {
Expand Down
2 changes: 1 addition & 1 deletion libs/filer/filer.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ type Filer interface {
Write(ctx context.Context, path string, reader io.Reader, mode ...WriteMode) error

// Read file at `path`.
Read(ctx context.Context, path string) (io.Reader, error)
Read(ctx context.Context, path string) (io.ReadCloser, error)

// Delete file or directory at `path`.
Delete(ctx context.Context, path string, mode ...DeleteMode) error
Expand Down
4 changes: 2 additions & 2 deletions libs/filer/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ func (f *fakeFiler) Write(ctx context.Context, p string, reader io.Reader, mode
return fmt.Errorf("not implemented")
}

func (f *fakeFiler) Read(ctx context.Context, p string) (io.Reader, error) {
func (f *fakeFiler) Read(ctx context.Context, p string) (io.ReadCloser, error) {
_, ok := f.entries[p]
if !ok {
return nil, fs.ErrNotExist
}

return strings.NewReader("foo"), nil
return io.NopCloser(strings.NewReader("foo")), nil
}

func (f *fakeFiler) Delete(ctx context.Context, p string, mode ...DeleteMode) error {
Expand Down
174 changes: 174 additions & 0 deletions libs/filer/local_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package filer

import (
"context"
"io"
"io/fs"
"os"
"path/filepath"

"golang.org/x/exp/slices"
)

// LocalClient implements the [Filer] interface for the local filesystem.
type LocalClient struct {
// File operations will be relative to this path.
root RootPath
}

func NewLocalClient(root string) (Filer, error) {
return &LocalClient{
root: NewRootPath(root),
}, nil
}

func (w *LocalClient) Write(ctx context.Context, name string, reader io.Reader, mode ...WriteMode) error {
absPath, err := w.root.Join(name)
if err != nil {
return err
}

flags := os.O_WRONLY | os.O_CREATE
if slices.Contains(mode, OverwriteIfExists) {
flags |= os.O_TRUNC
} else {
flags |= os.O_EXCL
}

absPath = filepath.FromSlash(absPath)
f, err := os.OpenFile(absPath, flags, 0644)
if os.IsNotExist(err) && slices.Contains(mode, CreateParentDirectories) {
// Create parent directories if they don't exist.
err = os.MkdirAll(filepath.Dir(absPath), 0755)
if err != nil {
return err
}
// Try again.
f, err = os.OpenFile(absPath, flags, 0644)
}

if err != nil {
switch {
case os.IsNotExist(err):
return NoSuchDirectoryError{path: absPath}
case os.IsExist(err):
return FileAlreadyExistsError{path: absPath}
default:
return err
}
}

_, err = io.Copy(f, reader)
cerr := f.Close()
if err == nil {
err = cerr
}

return err

}

func (w *LocalClient) Read(ctx context.Context, name string) (io.ReadCloser, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
}

// This stat call serves two purposes:
// 1. Checks file at path exists, and throws an error if it does not
// 2. Allows us to error out if the path is a directory
absPath = filepath.FromSlash(absPath)
stat, err := os.Stat(absPath)
if err != nil {
if os.IsNotExist(err) {
return nil, FileDoesNotExistError{path: absPath}
}
return nil, err
}

if stat.IsDir() {
return nil, NotAFile{path: absPath}
}

return os.Open(absPath)
}

func (w *LocalClient) Delete(ctx context.Context, name string, mode ...DeleteMode) error {
absPath, err := w.root.Join(name)
if err != nil {
return err
}

// Illegal to delete the root path.
if absPath == w.root.rootPath {
return CannotDeleteRootError{}
}

absPath = filepath.FromSlash(absPath)
err = os.Remove(absPath)

// Return early on success.
if err == nil {
return nil
}

if os.IsNotExist(err) {
return FileDoesNotExistError{path: absPath}
}

if os.IsExist(err) {
if slices.Contains(mode, DeleteRecursively) {
return os.RemoveAll(absPath)
}
return DirectoryNotEmptyError{path: absPath}
}

return err
}

func (w *LocalClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
}

absPath = filepath.FromSlash(absPath)
stat, err := os.Stat(absPath)
if err != nil {
if os.IsNotExist(err) {
return nil, NoSuchDirectoryError{path: absPath}
}
return nil, err
}

if !stat.IsDir() {
return nil, NotADirectory{path: absPath}
}

return os.ReadDir(absPath)
}

func (w *LocalClient) Mkdir(ctx context.Context, name string) error {
dirPath, err := w.root.Join(name)
if err != nil {
return err
}

dirPath = filepath.FromSlash(dirPath)
return os.MkdirAll(dirPath, 0755)
}

func (w *LocalClient) Stat(ctx context.Context, name string) (fs.FileInfo, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
}

absPath = filepath.FromSlash(absPath)
stat, err := os.Stat(absPath)
if os.IsNotExist(err) {
return nil, FileDoesNotExistError{path: absPath}
}

return stat, err
}
4 changes: 2 additions & 2 deletions libs/filer/workspace_files_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (w *WorkspaceFilesClient) Write(ctx context.Context, name string, reader io
return err
}

func (w *WorkspaceFilesClient) Read(ctx context.Context, name string) (io.Reader, error) {
func (w *WorkspaceFilesClient) Read(ctx context.Context, name string) (io.ReadCloser, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
Expand Down Expand Up @@ -184,7 +184,7 @@ func (w *WorkspaceFilesClient) Read(ctx context.Context, name string) (io.Reader
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
return io.NopCloser(bytes.NewReader(b)), nil
}

func (w *WorkspaceFilesClient) Delete(ctx context.Context, name string, mode ...DeleteMode) error {
Expand Down