From 28b0d3d47f915ff35fb6ffd03f6b5624fcea1081 Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Fri, 25 Mar 2016 11:53:54 -0700 Subject: [PATCH] Added lister --- dir/entry.go | 14 +++++- dir/lister.go | 95 ++++++++++++++++++++++++++++++++++++++++ dir/lister_nonwindows.go | 19 ++++++++ dir/lister_test.go | 80 +++++++++++++++++++++++++++++++++ dir/lister_windows.go | 7 +++ 5 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 dir/lister.go create mode 100644 dir/lister_nonwindows.go create mode 100644 dir/lister_test.go create mode 100644 dir/lister_windows.go diff --git a/dir/entry.go b/dir/entry.go index f1523d6cd..4b7a54e76 100644 --- a/dir/entry.go +++ b/dir/entry.go @@ -1,6 +1,7 @@ package dir import ( + "fmt" "io" "os" "time" @@ -54,6 +55,9 @@ func FileModeToType(mode os.FileMode) EntryType { } } +// Opener opens the contents of directory entry for reading. +type Opener func() (io.ReadCloser, error) + // Entry stores attributes of a single entry in a directory. type Entry struct { Name string @@ -64,7 +68,15 @@ type Entry struct { UserID uint32 GroupID uint32 ObjectID content.ObjectID - Open func() (io.ReadCloser, error) + + Open Opener +} + +func (e *Entry) String() string { + return fmt.Sprintf( + "name: '%v' type: %v modTime: %v size: %v oid: '%v' uid: %v gid: %v", + e.Name, e.Type, e.ModTime, e.Size, e.ObjectID, e.UserID, e.GroupID, + ) } // Directory contains access to contents of directory, both in original order and indexed by name. diff --git a/dir/lister.go b/dir/lister.go new file mode 100644 index 000000000..76bc0fec8 --- /dev/null +++ b/dir/lister.go @@ -0,0 +1,95 @@ +package dir + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" +) + +// Listing encapsulates list of items in a directory. +type Listing struct { + Directories []*Entry + Files []*Entry +} + +func (l Listing) String() string { + s := "" + for i, d := range l.Directories { + s += fmt.Sprintf("dir[%v] = %v\n", i, d) + } + for i, f := range l.Files { + s += fmt.Sprintf("file[%v] = %v\n", i, f) + } + + return s +} + +// Lister lists contents of filesystem directories. +type Lister interface { + List(path string) (Listing, error) +} + +type filesystemLister struct { +} + +func (l *filesystemLister) List(path string) (Listing, error) { + listing := Listing{} + entries, err := ioutil.ReadDir(path) + if err != nil { + return listing, err + } + + for _, fi := range entries { + switch fi.Name() { + case ".": + case "..": + continue + } + + e, err := entryFromFileSystemInfo(path, fi) + if err != nil { + return listing, err + } + + if e.Type == EntryTypeDirectory { + listing.Directories = append(listing.Directories, e) + } else { + listing.Files = append(listing.Files, e) + } + } + + return listing, nil +} + +// NewFilesystemLister creates a Lister that can be used to list contents of filesystem directories. +func NewFilesystemLister() Lister { + return &filesystemLister{} +} + +func openFileFunc(parentDir string, fi os.FileInfo) Opener { + return func() (io.ReadCloser, error) { + return os.Open(filepath.Join(parentDir, fi.Name())) + } +} + +func entryFromFileSystemInfo(parentDir string, fi os.FileInfo) (*Entry, error) { + e := &Entry{ + Name: fi.Name(), + Mode: int16(fi.Mode().Perm()), + ModTime: fi.ModTime(), + Type: FileModeToType(fi.Mode()), + Open: openFileFunc(parentDir, fi), + } + + if e.Type == EntryTypeFile { + e.Size = fi.Size() + } + + if err := populatePlatformSpecificEntryDetails(e, fi); err != nil { + return nil, err + } + + return e, nil +} diff --git a/dir/lister_nonwindows.go b/dir/lister_nonwindows.go new file mode 100644 index 000000000..a82d64758 --- /dev/null +++ b/dir/lister_nonwindows.go @@ -0,0 +1,19 @@ +// +build !windows + +package dir + +import ( + "fmt" + "os" + "syscall" +) + +func populatePlatformSpecificEntryDetails(e *Entry, fileInfo os.FileInfo) error { + if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok { + e.UserID = stat.Uid + e.GroupID = stat.Gid + return nil + } + + return fmt.Errorf("unable to retrieve platform-specific file information") +} diff --git a/dir/lister_test.go b/dir/lister_test.go new file mode 100644 index 000000000..33fff361c --- /dev/null +++ b/dir/lister_test.go @@ -0,0 +1,80 @@ +package dir + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "testing" +) + +func TestLister(t *testing.T) { + var err error + + tmp, err := ioutil.TempDir("", "kopia") + if err != nil { + t.Errorf("cannot create temp directory: %v", err) + return + } + + defer os.RemoveAll(tmp) + + lister := NewFilesystemLister() + + var listing Listing + + // Try listing directory that does not exist. + listing, err = lister.List(fmt.Sprintf("/no-such-dir-%v", time.Now().Nanosecond())) + if err == nil { + t.Errorf("expected error when listing directory that does not exist.") + } + + // Now list an empty directory that does not exist. + listing, err = lister.List(tmp) + if err != nil { + t.Errorf("error when listing empty directory: %v", err) + } + + if len(listing.Directories)+len(listing.Files) > 0 { + t.Errorf("expected empty directory, got %v", listing) + } + + // Now list a directory with 3 files. + ioutil.WriteFile(filepath.Join(tmp, "f3"), []byte{1, 2, 3}, 0777) + ioutil.WriteFile(filepath.Join(tmp, "f2"), []byte{1, 2, 3, 4}, 0777) + ioutil.WriteFile(filepath.Join(tmp, "f1"), []byte{1, 2, 3, 4, 5}, 0777) + + os.Mkdir(filepath.Join(tmp, "z"), 0777) + os.Mkdir(filepath.Join(tmp, "y"), 0777) + + listing, err = lister.List(tmp) + if err != nil { + t.Errorf("error when listing directory with files: %v", err) + } + + if len(listing.Files) != 3 || len(listing.Directories) != 2 { + t.Errorf("expected 3 files, got: %v", listing) + } else { + goodCount := 0 + if listing.Files[0].Name == "f1" && listing.Files[0].Size == 5 && listing.Files[0].Type == EntryTypeFile { + goodCount++ + } + if listing.Files[1].Name == "f2" && listing.Files[1].Size == 4 && listing.Files[1].Type == EntryTypeFile { + goodCount++ + } + if listing.Files[2].Name == "f3" && listing.Files[2].Size == 3 && listing.Files[2].Type == EntryTypeFile { + goodCount++ + } + if listing.Directories[0].Name == "y" && listing.Directories[0].Size == 0 && listing.Directories[0].Type == EntryTypeDirectory { + goodCount++ + } + if listing.Directories[1].Name == "z" && listing.Directories[1].Size == 0 && listing.Directories[1].Type == EntryTypeDirectory { + goodCount++ + } + if goodCount != 5 { + t.Errorf("invalid listing data:\n%v", listing) + } + } +} diff --git a/dir/lister_windows.go b/dir/lister_windows.go new file mode 100644 index 000000000..1dec9d18e --- /dev/null +++ b/dir/lister_windows.go @@ -0,0 +1,7 @@ +package dir + +import "os" + +func populatePlatformSpecificEntryDetails(e *Entry, fileInfo os.FileInfo) error { + return nil +}