Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bundle: Implement a DirectoryLoader for fs.FS #3493

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 73 additions & 0 deletions bundle/filefs.go
@@ -0,0 +1,73 @@
// +build go1.16
simongottschlag marked this conversation as resolved.
Show resolved Hide resolved

package bundle

import (
"fmt"
"io"
"io/fs"
"sync"
)

const (
defaultFSLoaderRoot = "."
)

type dirLoaderFS struct {
sync.Mutex
filesystem fs.FS
files []string
idx int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: array indices cannot be negative, so this should be a uint.

}

// NewFSLoader returns a basic DirectoryLoader implementation
// that will load files from a fs.FS interface
func NewFSLoader(filesystem fs.FS) (DirectoryLoader, error) {
d := dirLoaderFS{
filesystem: filesystem,
}

err := fs.WalkDir(d.filesystem, defaultFSLoaderRoot, d.walkDir)
if err != nil {
return nil, fmt.Errorf("failed to list files: %w", err)
}

return &d, nil
}

func (d *dirLoaderFS) walkDir(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}

if dirEntry != nil && dirEntry.Type().IsRegular() {
d.files = append(d.files, path)
}

return nil
}

// NextFile iterates to the next file in the directory tree
// and returns a file Descriptor for the file.
func (d *dirLoaderFS) NextFile() (*Descriptor, error) {
d.Lock()
defer d.Unlock()

// If done reading files then just return io.EOF
// errors for each NextFile() call
if d.idx >= len(d.files) {
return nil, io.EOF
}

fileName := d.files[d.idx]
d.idx++
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This incrementing is the only bit of this method that needs to be synchronized. Thus, it is probably better to use atomic.AddUint32 since that will avoid the mutex alltogether. In order for this to work, the addition must go at the top of the method, before it is used.


fh, err := d.filesystem.Open(fileName)
if err != nil {
return nil, fmt.Errorf("failed to open file %s: %w", fileName, err)
}

fileNameWithSlash := fmt.Sprintf("/%s", fileName)
f := newDescriptor(fileNameWithSlash, fileNameWithSlash, fh).withCloser(fh)
return f, nil
}
26 changes: 26 additions & 0 deletions bundle/filefs_test.go
@@ -0,0 +1,26 @@
// +build go1.16

package bundle

import (
"strings"
"testing"
"testing/fstest"
)

func TestFSLoader(t *testing.T) {
archiveFS := make(fstest.MapFS)
for k, v := range archiveFiles {
file := strings.TrimPrefix(k, "/")
archiveFS[file] = &fstest.MapFile{
Data: []byte(v),
}
}

loader, err := NewFSLoader(archiveFS)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}

testLoader(t, loader, "", archiveFiles)
}