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

Add caching for explored directories #2

Merged
merged 11 commits into from
Oct 16, 2024
101 changes: 101 additions & 0 deletions fileinfo/fileInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fileinfo

import (
"errors"
"io/fs"
"os"
"path/filepath"
"slices"
Expand All @@ -16,6 +17,8 @@ type FileInfo struct {
Name string
FileType string
Size int
Children []string
Checked bool
}

var errBadDescriptor = errors.New("bad file descriptor")
Expand Down Expand Up @@ -126,3 +129,101 @@ func GetRootInfo(root string) ([]FileInfo, error) {

return infoList, err
}

func GenerateFileMap(m map[string]FileInfo, root string) (map[string]FileInfo, error) {
if m == nil {
m = map[string]FileInfo{}
}
f := getMapFillerFunc(m)

if walkErr := filepath.WalkDir(root, f); walkErr != nil {
return nil, walkErr
}

updateDirSizes(m, root)

return m, nil
}

func getMapFillerFunc(m map[string]FileInfo) func(path string, d fs.DirEntry, err error) error {
return func(path string, d fs.DirEntry, err error) error {
if err != nil {
if d.IsDir() {
return filepath.SkipDir
}
return err
}

parent := filepath.Dir(path)
if fi, exists := m[parent]; exists {
if !fi.Checked {
fi.Children = append(fi.Children, path)
m[parent] = fi
}
}

if _, exists := m[path]; exists {
return filepath.SkipDir
}

if d.IsDir() {
m[path] = FileInfo{Name: path, FileType: "dir", Size: 0}
return nil
}

info, err := d.Info()
if err != nil {
if errors.Is(err, fs.ErrPermission) {
return nil
}
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
m[path] = FileInfo{Name: path, FileType: "file", Size: int(info.Size())}
return nil
}
}

func updateDirSizes(m map[string]FileInfo, root string) {
fi := m[root]
if fi.FileType == "file" {
fi.Checked = true
m[root] = fi
}
if fi.Checked {
return
}
for _, c := range fi.Children {
updateDirSizes(m, c)
fi.Size += m[c].Size
}
fi.Checked = true
m[root] = fi
}

func GetSortedDirs(m map[string]FileInfo, root string) []FileInfo {
list := []FileInfo{}

for _, c := range m[root].Children {
list = append(list, m[c])
}

slices.SortFunc(list, func(a, b FileInfo) int {
return b.Size - a.Size
})

return list
}

func CleanChildren(m map[string]FileInfo, dir string) {
delete(m, dir)

for path, fi := range m {
fi.Children = slices.DeleteFunc(fi.Children, func(item string) bool {
return item == dir
})
m[path] = fi
}
}
78 changes: 78 additions & 0 deletions fileinfo/fileInfo_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fileinfo

import (
"reflect"
"slices"
"testing"
)
Expand All @@ -22,3 +23,80 @@ func TestGetRootInfo(t *testing.T) {
t.Errorf("slices are not equal. Expected %v, but got %v", expected, infoList)
}
}

func TestGenerateFileMap(t *testing.T) {
m, err := GenerateFileMap(nil, "testfiles")
if err != nil {
t.Error(err)
}
expected := map[string]FileInfo{
"testfiles": {Name: "testfiles", FileType: "dir", Size: 13, Children: []string{"testfiles/dir1", "testfiles/file1.txt", "testfiles/file2.txt"}, Checked: true},
"testfiles/dir1": {Name: "testfiles/dir1", FileType: "dir", Size: 10, Children: []string{"testfiles/dir1/dir2", "testfiles/dir1/file3.txt"}, Checked: true},
"testfiles/dir1/dir2": {Name: "testfiles/dir1/dir2", FileType: "dir", Size: 7, Children: []string{"testfiles/dir1/dir2/file4.txt"}, Checked: true},
"testfiles/dir1/dir2/file4.txt": {Name: "testfiles/dir1/dir2/file4.txt", FileType: "file", Size: 7, Children: []string(nil), Checked: true},
"testfiles/dir1/file3.txt": {Name: "testfiles/dir1/file3.txt", FileType: "file", Size: 3, Children: []string(nil), Checked: true},
"testfiles/file1.txt": {Name: "testfiles/file1.txt", FileType: "file", Size: 1, Children: []string(nil), Checked: true},
"testfiles/file2.txt": {Name: "testfiles/file2.txt", FileType: "file", Size: 2, Children: []string(nil), Checked: true},
}

if !reflect.DeepEqual(m, expected) {
t.Errorf("expected %v, but got %v", expected, m)
}
}

func TestGetSortedDirs(t *testing.T) {
m, err := GenerateFileMap(nil, "testfiles")
if err != nil {
t.Error(err)
}

list := GetSortedDirs(m, "testfiles")
expected := []FileInfo{{Name: "testfiles/dir1", FileType: "dir", Size: 10}, {Name: "testfiles/file2.txt", FileType: "file", Size: 2}, {Name: "testfiles/file1.txt", FileType: "file", Size: 1}}

if slices.CompareFunc(expected, list, func(a, b FileInfo) int {
if a.FileType != b.FileType || a.Name != b.Name || a.Size != b.Size {
return -1
}
return 0
}) != 0 {
t.Errorf("slices are not equal. Expected %v, but got %v", expected, list)
}
}

func TestCleanChildren(t *testing.T) {
m := map[string]FileInfo{
"testfiles": {Name: "testfiles", FileType: "dir", Size: 13, Children: []string{"testfiles/dir1", "testfiles/file1.txt", "testfiles/file2.txt"}, Checked: true},
"testfiles/dir1": {Name: "testfiles/dir1", FileType: "dir", Size: 10, Children: []string{"testfiles/dir1/dir2", "testfiles/dir1/file3.txt"}, Checked: true},
}

expected := map[string]FileInfo{
"testfiles": {Name: "testfiles", FileType: "dir", Size: 13, Children: []string{"testfiles/file1.txt", "testfiles/file2.txt"}, Checked: true},
}

CleanChildren(m, "testfiles/dir1")
t.Log(m)

if !reflect.DeepEqual(expected, m) {
t.Errorf("expected %v, got %v", expected, m)
}
}

func BenchmarkGetRootInfo(b *testing.B) {
for range b.N {
_, err := GetRootInfo("testfiles")
if err != nil {
b.Error(err)
}
}
}

func BenchmarkGetSortedDirs(b *testing.B) {
for range b.N {
m, err := GenerateFileMap(nil, "testfiles")
if err != nil {
b.Error(err)
}

_ = GetSortedDirs(m, "testfiles")
}
}
24 changes: 12 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ module github.com/frogfreg/godu
go 1.23.2

require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.7.1
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.1.1
github.com/charmbracelet/lipgloss v0.13.0
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.6.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
55 changes: 29 additions & 26 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
Loading