From 503fa74be58ba6ac98c9cec887c978ff0bc90f22 Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Mon, 30 Dec 2019 19:16:35 -0800 Subject: [PATCH] fs: added Directory.Child() method to look up children by name without necessarily loading all of them --- fs/cachefs/cachefs.go | 9 +++++ fs/entry.go | 21 ++++++++++++ fs/localfs/local_fs.go | 15 +++++++++ fs/localfs/local_fs_test.go | 41 +++++++++++++++++++++++ fs/loggingfs/loggingfs.go | 13 +++++++ internal/mockfs/mockfs.go | 4 +++ snapshot/snapshotfs/all_sources.go | 4 +++ snapshot/snapshotfs/repofs.go | 4 +++ snapshot/snapshotfs/source_directories.go | 6 ++++ snapshot/snapshotfs/source_snapshots.go | 6 ++++ 10 files changed, 123 insertions(+) diff --git a/fs/cachefs/cachefs.go b/fs/cachefs/cachefs.go index 3c2b1e03a..8c4409abb 100644 --- a/fs/cachefs/cachefs.go +++ b/fs/cachefs/cachefs.go @@ -21,6 +21,15 @@ type directory struct { fs.Directory } +func (d *directory) Child(ctx context.Context, name string) (fs.Entry, error) { + e, err := d.Directory.Child(ctx, name) + if err != nil { + return nil, err + } + + return wrapWithContext(e, d.ctx), nil +} + func (d *directory) Readdir(ctx context.Context) (fs.Entries, error) { entries, err := d.ctx.cacher.Readdir(ctx, d.Directory) if err != nil { diff --git a/fs/entry.go b/fs/entry.go index 0b109b710..8e472e306 100644 --- a/fs/entry.go +++ b/fs/entry.go @@ -2,6 +2,7 @@ package fs import ( "context" + "errors" "io" "os" "sort" @@ -40,10 +41,30 @@ type File interface { // Directory represents contents of a directory. type Directory interface { Entry + Child(ctx context.Context, name string) (Entry, error) Readdir(ctx context.Context) (Entries, error) Summary() *DirectorySummary } +// ErrEntryNotFound is returned when an entry is not found. +var ErrEntryNotFound = errors.New("entry not found") + +// ReadDirAndFindChild reads all entries from a directory and returns one by name. +// This is a convenience function that may be helpful in implementations of Directory.Child() +func ReadDirAndFindChild(ctx context.Context, d Directory, name string) (Entry, error) { + children, err := d.Readdir(ctx) + if err != nil { + return nil, err + } + + e := children.FindByName(name) + if e == nil { + return nil, ErrEntryNotFound + } + + return e, nil +} + // DirectorySummary represents summary information about a directory. type DirectorySummary struct { TotalFileSize int64 `json:"size"` diff --git a/fs/localfs/local_fs.go b/fs/localfs/local_fs.go index b931b3c76..2ea3dc4df 100644 --- a/fs/localfs/local_fs.go +++ b/fs/localfs/local_fs.go @@ -101,6 +101,21 @@ func (fsd *filesystemDirectory) Summary() *fs.DirectorySummary { return nil } +func (fsd *filesystemDirectory) Child(ctx context.Context, name string) (fs.Entry, error) { + fullPath := fsd.fullPath() + + st, err := os.Stat(filepath.Join(fullPath, name)) + if err != nil { + if os.IsNotExist(err) { + return nil, fs.ErrEntryNotFound + } + + return nil, errors.Wrap(err, "unable to get child") + } + + return entryFromChildFileInfo(st, fullPath) +} + func (fsd *filesystemDirectory) Readdir(ctx context.Context) (fs.Entries, error) { fullPath := fsd.fullPath() diff --git a/fs/localfs/local_fs_test.go b/fs/localfs/local_fs_test.go index 80e8913c0..a177fd9c0 100644 --- a/fs/localfs/local_fs_test.go +++ b/fs/localfs/local_fs_test.go @@ -97,6 +97,47 @@ func TestFiles(t *testing.T) { t.Logf("e[%v] = %v %v %v", i, e.Name(), e.Size(), e.Mode()) } } + + verifyChild(t, dir) +} + +func verifyChild(t *testing.T, dir fs.Directory) { + ctx := context.Background() + + child, err := dir.Child(ctx, "f3") + if err != nil { + t.Errorf("child error: %v", err) + } + + if _, err = dir.Child(ctx, "f4"); err != fs.ErrEntryNotFound { + t.Errorf("unexpected child error: %v", err) + } + + if got, want := child.Name(), "f3"; got != want { + t.Errorf("unexpected child name: %v, want %v", got, want) + } + + if got, want := child.Size(), int64(3); got != want { + t.Errorf("unexpected child size: %v, want %v", got, want) + } + + if _, err = fs.ReadDirAndFindChild(ctx, dir, "f4"); err != fs.ErrEntryNotFound { + t.Errorf("unexpected child error: %v", err) + } + + // read child again, this time using ReadAndFindChild + child2, err := fs.ReadDirAndFindChild(ctx, dir, "f3") + if err != nil { + t.Errorf("child2 error: %v", err) + } + + if got, want := child2.Name(), "f3"; got != want { + t.Errorf("unexpected child2 name: %v, want %v", got, want) + } + + if got, want := child2.Size(), int64(3); got != want { + t.Errorf("unexpected child2 size: %v, want %v", got, want) + } } func assertNoError(t *testing.T, err error) { diff --git a/fs/loggingfs/loggingfs.go b/fs/loggingfs/loggingfs.go index fd3e5dac0..fe6af0769 100644 --- a/fs/loggingfs/loggingfs.go +++ b/fs/loggingfs/loggingfs.go @@ -22,6 +22,19 @@ type loggingDirectory struct { fs.Directory } +func (ld *loggingDirectory) Child(ctx context.Context, name string) (fs.Entry, error) { + t0 := time.Now() + entry, err := ld.Directory.Child(ctx, name) + dt := time.Since(t0) + ld.options.printf(ld.options.prefix+"Child(%v) took %v and returned %v", ld.relativePath, dt, err) + + if err != nil { + return nil, err + } + + return wrapWithOptions(entry, ld.options, ld.relativePath+"/"+entry.Name()), nil +} + func (ld *loggingDirectory) Readdir(ctx context.Context) (fs.Entries, error) { t0 := time.Now() entries, err := ld.Directory.Readdir(ctx) diff --git a/internal/mockfs/mockfs.go b/internal/mockfs/mockfs.go index 504424c5a..946375691 100644 --- a/internal/mockfs/mockfs.go +++ b/internal/mockfs/mockfs.go @@ -173,6 +173,10 @@ func (imd *Directory) FailReaddir(err error) { imd.readdirError = err } +func (imd *Directory) Child(ctx context.Context, name string) (fs.Entry, error) { + return fs.ReadDirAndFindChild(ctx, imd, name) +} + // Readdir gets the contents of a directory. func (imd *Directory) Readdir(ctx context.Context) (fs.Entries, error) { if imd.readdirError != nil { diff --git a/snapshot/snapshotfs/all_sources.go b/snapshot/snapshotfs/all_sources.go index a1891874e..1ccfc40ce 100644 --- a/snapshot/snapshotfs/all_sources.go +++ b/snapshot/snapshotfs/all_sources.go @@ -47,6 +47,10 @@ func (s *repositoryAllSources) Sys() interface{} { return nil } +func (s *repositoryAllSources) Child(ctx context.Context, name string) (fs.Entry, error) { + return fs.ReadDirAndFindChild(ctx, s, name) +} + func (s *repositoryAllSources) Readdir(ctx context.Context) (fs.Entries, error) { srcs, err := snapshot.ListSources(ctx, s.rep) if err != nil { diff --git a/snapshot/snapshotfs/repofs.go b/snapshot/snapshotfs/repofs.go index 2ac4ce8e7..e6bd4dfae 100644 --- a/snapshot/snapshotfs/repofs.go +++ b/snapshot/snapshotfs/repofs.go @@ -79,6 +79,10 @@ func (rd *repositoryDirectory) Summary() *fs.DirectorySummary { return rd.summary } +func (rd *repositoryDirectory) Child(ctx context.Context, name string) (fs.Entry, error) { + return fs.ReadDirAndFindChild(ctx, rd, name) +} + func (rd *repositoryDirectory) Readdir(ctx context.Context) (fs.Entries, error) { r, err := rd.repo.Objects.Open(ctx, rd.metadata.ObjectID) if err != nil { diff --git a/snapshot/snapshotfs/source_directories.go b/snapshot/snapshotfs/source_directories.go index 2e8acbc6b..8512a1555 100644 --- a/snapshot/snapshotfs/source_directories.go +++ b/snapshot/snapshotfs/source_directories.go @@ -47,6 +47,10 @@ func (s *sourceDirectories) Owner() fs.OwnerInfo { return fs.OwnerInfo{} } +func (s *sourceDirectories) Child(ctx context.Context, name string) (fs.Entry, error) { + return fs.ReadDirAndFindChild(ctx, s, name) +} + func (s *sourceDirectories) Readdir(ctx context.Context) (fs.Entries, error) { sources, err := snapshot.ListSources(ctx, s.rep) if err != nil { @@ -67,3 +71,5 @@ func (s *sourceDirectories) Readdir(ctx context.Context) (fs.Entries, error) { return result, nil } + +var _ fs.Directory = (*sourceDirectories)(nil) diff --git a/snapshot/snapshotfs/source_snapshots.go b/snapshot/snapshotfs/source_snapshots.go index 8609a877e..45d284c99 100644 --- a/snapshot/snapshotfs/source_snapshots.go +++ b/snapshot/snapshotfs/source_snapshots.go @@ -56,6 +56,10 @@ func (s *sourceSnapshots) Summary() *fs.DirectorySummary { return nil } +func (s *sourceSnapshots) Child(ctx context.Context, name string) (fs.Entry, error) { + return fs.ReadDirAndFindChild(ctx, s, name) +} + func (s *sourceSnapshots) Readdir(ctx context.Context) (fs.Entries, error) { manifests, err := snapshot.ListSnapshots(ctx, s.rep, s.src) if err != nil { @@ -94,3 +98,5 @@ func (s *sourceSnapshots) Readdir(ctx context.Context) (fs.Entries, error) { return result, nil } + +var _ fs.Directory = (*sourceSnapshots)(nil)