Make filer.Filer return fs.DirEntry from ReadDir (#415)

## Changes

This allows for compatibility with the stdlib functions in io/fs.

## Tests

Integration tests pass.
This commit is contained in:
Pieter Noordhuis 2023-05-31 12:22:26 +00:00 committed by GitHub
parent 27df4e765c
commit 42cd8daee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 42 deletions

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"strings"
"testing"
@ -75,6 +76,7 @@ func runFilerReadWriteTest(t *testing.T, ctx context.Context, f filer.Filer) {
func runFilerReadDirTest(t *testing.T, ctx context.Context, f filer.Filer) {
var err error
var info fs.FileInfo
// We start with an empty directory.
entries, err := f.ReadDir(ctx, ".")
@ -105,23 +107,32 @@ func runFilerReadDirTest(t *testing.T, ctx context.Context, f filer.Filer) {
entries, err = f.ReadDir(ctx, ".")
require.NoError(t, err)
assert.Len(t, entries, 2)
assert.Equal(t, "dir", entries[0].Name)
assert.Equal(t, "hello.txt", entries[1].Name)
assert.Greater(t, entries[1].ModTime.Unix(), int64(0))
assert.Equal(t, "dir", entries[0].Name())
assert.True(t, entries[0].IsDir())
assert.Equal(t, "hello.txt", entries[1].Name())
assert.False(t, entries[1].IsDir())
info, err = entries[1].Info()
require.NoError(t, err)
assert.Greater(t, info.ModTime().Unix(), int64(0))
// Expect two entries in the directory.
entries, err = f.ReadDir(ctx, "/dir")
require.NoError(t, err)
assert.Len(t, entries, 2)
assert.Equal(t, "a", entries[0].Name)
assert.Equal(t, "world.txt", entries[1].Name)
assert.Greater(t, entries[1].ModTime.Unix(), int64(0))
assert.Equal(t, "a", entries[0].Name())
assert.True(t, entries[0].IsDir())
assert.Equal(t, "world.txt", entries[1].Name())
assert.False(t, entries[1].IsDir())
info, err = entries[1].Info()
require.NoError(t, err)
assert.Greater(t, info.ModTime().Unix(), int64(0))
// Expect a single entry in the nested path.
entries, err = f.ReadDir(ctx, "/dir/a/b")
require.NoError(t, err)
assert.Len(t, entries, 1)
assert.Equal(t, "c", entries[0].Name)
assert.Equal(t, "c", entries[0].Name())
assert.True(t, entries[0].IsDir())
}
func temporaryWorkspaceDir(t *testing.T, w *databricks.WorkspaceClient) string {

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
"io/fs"
"net/http"
"path"
"sort"
@ -15,6 +16,52 @@ import (
"golang.org/x/exp/slices"
)
// Type that implements fs.DirEntry for DBFS.
type dbfsDirEntry struct {
dbfsFileInfo
}
func (entry dbfsDirEntry) Type() fs.FileMode {
typ := fs.ModePerm
if entry.fi.IsDir {
typ |= fs.ModeDir
}
return typ
}
func (entry dbfsDirEntry) Info() (fs.FileInfo, error) {
return entry.dbfsFileInfo, nil
}
// Type that implements fs.FileInfo for DBFS.
type dbfsFileInfo struct {
fi files.FileInfo
}
func (info dbfsFileInfo) Name() string {
return path.Base(info.fi.Path)
}
func (info dbfsFileInfo) Size() int64 {
return info.fi.FileSize
}
func (info dbfsFileInfo) Mode() fs.FileMode {
return fs.ModePerm
}
func (info dbfsFileInfo) ModTime() time.Time {
return time.UnixMilli(info.fi.ModificationTime)
}
func (info dbfsFileInfo) IsDir() bool {
return info.fi.IsDir
}
func (info dbfsFileInfo) Sys() any {
return nil
}
// DbfsClient implements the [Filer] interface for the DBFS backend.
type DbfsClient struct {
workspaceClient *databricks.WorkspaceClient
@ -152,7 +199,7 @@ func (w *DbfsClient) Delete(ctx context.Context, name string) error {
})
}
func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]FileInfo, error) {
func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
@ -175,17 +222,13 @@ func (w *DbfsClient) ReadDir(ctx context.Context, name string) ([]FileInfo, erro
return nil, err
}
info := make([]FileInfo, len(res.Files))
info := make([]fs.DirEntry, len(res.Files))
for i, v := range res.Files {
info[i] = FileInfo{
Name: path.Base(v.Path),
Size: v.FileSize,
ModTime: time.UnixMilli(v.ModificationTime),
}
info[i] = dbfsDirEntry{dbfsFileInfo: dbfsFileInfo{fi: v}}
}
// Sort by name for parity with os.ReadDir.
sort.Slice(info, func(i, j int) bool { return info[i].Name < info[j].Name })
sort.Slice(info, func(i, j int) bool { return info[i].Name() < info[j].Name() })
return info, nil
}

View File

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"io"
"time"
"io/fs"
)
type WriteMode int
@ -14,22 +14,6 @@ const (
CreateParentDirectories = iota << 1
)
// FileInfo abstracts over file information from different file systems.
// Inspired by https://pkg.go.dev/io/fs#FileInfo.
type FileInfo struct {
// The type of the file in workspace.
Type string
// Base name.
Name string
// Size in bytes.
Size int64
// Modification time.
ModTime time.Time
}
type FileAlreadyExistsError struct {
path string
}
@ -68,7 +52,7 @@ type Filer interface {
Delete(ctx context.Context, path string) error
// Return contents of directory at `path`.
ReadDir(ctx context.Context, path string) ([]FileInfo, error)
ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error)
// Creates directory at `path`, creating any intermediate directories as required.
Mkdir(ctx context.Context, path string) error

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"path"
@ -20,6 +21,53 @@ import (
"golang.org/x/exp/slices"
)
// Type that implements fs.DirEntry for WSFS.
type wsfsDirEntry struct {
wsfsFileInfo
}
func (entry wsfsDirEntry) Type() fs.FileMode {
return entry.wsfsFileInfo.Mode()
}
func (entry wsfsDirEntry) Info() (fs.FileInfo, error) {
return entry.wsfsFileInfo, nil
}
// Type that implements fs.FileInfo for WSFS.
type wsfsFileInfo struct {
oi workspace.ObjectInfo
}
func (info wsfsFileInfo) Name() string {
return path.Base(info.oi.Path)
}
func (info wsfsFileInfo) Size() int64 {
return info.oi.Size
}
func (info wsfsFileInfo) Mode() fs.FileMode {
switch info.oi.ObjectType {
case workspace.ObjectTypeDirectory:
return fs.ModeDir
default:
return fs.ModePerm
}
}
func (info wsfsFileInfo) ModTime() time.Time {
return time.UnixMilli(info.oi.ModifiedAt)
}
func (info wsfsFileInfo) IsDir() bool {
return info.oi.ObjectType == workspace.ObjectTypeDirectory
}
func (info wsfsFileInfo) Sys() any {
return nil
}
// WorkspaceFilesClient implements the files-in-workspace API.
// NOTE: This API is available for files under /Repos if a workspace has files-in-repos enabled.
@ -165,7 +213,7 @@ func (w *WorkspaceFilesClient) Delete(ctx context.Context, name string) error {
return err
}
func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]FileInfo, error) {
func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {
absPath, err := w.root.Join(name)
if err != nil {
return nil, err
@ -189,18 +237,13 @@ func (w *WorkspaceFilesClient) ReadDir(ctx context.Context, name string) ([]File
return nil, err
}
info := make([]FileInfo, len(objects))
info := make([]fs.DirEntry, len(objects))
for i, v := range objects {
info[i] = FileInfo{
Type: string(v.ObjectType),
Name: path.Base(v.Path),
Size: v.Size,
ModTime: time.UnixMilli(v.ModifiedAt),
}
info[i] = wsfsDirEntry{wsfsFileInfo{oi: v}}
}
// Sort by name for parity with os.ReadDir.
sort.Slice(info, func(i, j int) bool { return info[i].Name < info[j].Name })
sort.Slice(info, func(i, j int) bool { return info[i].Name() < info[j].Name() })
return info, nil
}