diff --git a/analyze/dir.go b/analyze/dir.go index fc329a844..112333743 100644 --- a/analyze/dir.go +++ b/analyze/dir.go @@ -64,10 +64,10 @@ func processDir(path string, progress *CurrentProgress, concurrencyLimitChannel wait.Add(1) go func() { concurrencyLimitChannel <- true - file = processDir(entryPath, progress, concurrencyLimitChannel, wait, ignoreDir) - file.Parent = &dir + subdir := processDir(entryPath, progress, concurrencyLimitChannel, wait, ignoreDir) + subdir.Parent = &dir mutex.Lock() - dir.Files = append(dir.Files, file) + dir.Files = append(dir.Files, subdir) mutex.Unlock() <-concurrencyLimitChannel wait.Done() diff --git a/analyze/file.go b/analyze/file.go index 267859089..7fba388eb 100644 --- a/analyze/file.go +++ b/analyze/file.go @@ -75,6 +75,16 @@ func (s Files) Find(file *File) int { return -1 } +// FindByName searches name in Files and returns its index, or -1 +func (s Files) FindByName(name string) int { + for i, item := range s { + if item.Name == name { + return i + } + } + return -1 +} + // Remove removes File from Files func (s Files) Remove(file *File) Files { index := s.Find(file) @@ -83,3 +93,12 @@ func (s Files) Remove(file *File) Files { } return append(s[:index], s[index+1:]...) } + +// RemoveByName removes File from Files +func (s Files) RemoveByName(name string) Files { + index := s.FindByName(name) + if index == -1 { + return s + } + return append(s[:index], s[index+1:]...) +} diff --git a/analyze/file_test.go b/analyze/file_test.go index 0a37ac3a5..40233b907 100644 --- a/analyze/file_test.go +++ b/analyze/file_test.go @@ -58,6 +58,33 @@ func TestRemove(t *testing.T) { assert.Equal(t, file2, dir.Files[0]) } +func TestRemoveByName(t *testing.T) { + dir := File{ + Name: "xxx", + Size: 5, + ItemCount: 2, + } + + file := &File{ + Name: "yyy", + Size: 2, + ItemCount: 1, + Parent: &dir, + } + file2 := &File{ + Name: "zzz", + Size: 3, + ItemCount: 1, + Parent: &dir, + } + dir.Files = []*File{file, file2} + + dir.Files = dir.Files.RemoveByName("yyy") + + assert.Equal(t, 1, len(dir.Files)) + assert.Equal(t, file2, dir.Files[0]) +} + func TestRemoveNotInDir(t *testing.T) { dir := File{ Name: "xxx", @@ -85,6 +112,33 @@ func TestRemoveNotInDir(t *testing.T) { assert.Equal(t, 1, len(dir.Files)) } +func TestRemoveByNameNotInDir(t *testing.T) { + dir := File{ + Name: "xxx", + Size: 5, + ItemCount: 2, + } + + file := &File{ + Name: "yyy", + Size: 2, + ItemCount: 1, + Parent: &dir, + } + file2 := &File{ + Name: "zzz", + Size: 3, + ItemCount: 1, + } + dir.Files = []*File{file} + + assert.Equal(t, -1, dir.Files.Find(file2)) + + dir.Files = dir.Files.RemoveByName("zzz") + + assert.Equal(t, 1, len(dir.Files)) +} + func TestRemoveFile(t *testing.T) { dir := &File{ Name: "xxx", diff --git a/cli/cli.go b/cli/cli.go index dac9fe5bd..51c0fd528 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -19,6 +19,7 @@ const helpTextColorized = ` [red]enter, right, l [white]Select directory/device [red]left, h [white]Go to parent directory [red]d [white]Delete selected file or directory + [red]r [white]Rescan current directory [red]n [white]Sort by name (asc/desc) [red]s [white]Sort by size (asc/desc) [red]c [white]Sort by items (asc/desc) @@ -28,6 +29,7 @@ const helpText = ` [::b]enter, right, l [white:black:-]Select directory/device [::b]left, h [white:black:-]Go to parent directory [::b]d [white:black:-]Delete selected file or directory + [::b]r [white:black:-]Rescan current directory [::b]n [white:black:-]Sort by name (asc/desc) [::b]s [white:black:-]Sort by size (asc/desc) [::b]c [white:black:-]Sort by items (asc/desc) @@ -46,6 +48,7 @@ type UI struct { currentDir *analyze.File devices []*analyze.Device analyzer analyze.Analyzer + topDir *analyze.File topDirPath string currentDirPath string askBeforeDelete bool @@ -152,8 +155,8 @@ func (ui *UI) ListDevices() { } // AnalyzePath analyzes recursively disk usage in given path -func (ui *UI) AnalyzePath(path string, analyzer analyze.Analyzer) { - ui.topDirPath, _ = filepath.Abs(path) +func (ui *UI) AnalyzePath(path string, analyzer analyze.Analyzer, parentDir *analyze.File) { + abspath, _ := filepath.Abs(path) ui.progress = tview.NewTextView().SetText("Scanning...") ui.progress.SetBorder(true).SetBorderPadding(2, 2, 2, 2) @@ -180,7 +183,17 @@ func (ui *UI) AnalyzePath(path string, analyzer analyze.Analyzer) { go ui.updateProgress(progress) go func() { - ui.currentDir = analyzer(ui.topDirPath, progress, ui.ShouldDirBeIgnored) + ui.currentDir = analyzer(abspath, progress, ui.ShouldDirBeIgnored) + + if parentDir != nil { + ui.currentDir.Parent = parentDir + parentDir.Files = parentDir.Files.RemoveByName(ui.currentDir.Name) + parentDir.Files = append(parentDir.Files, ui.currentDir) + ui.topDir.UpdateStats() + } else { + ui.topDirPath = abspath + ui.topDir = ui.currentDir + } ui.app.QueueUpdateDraw(func() { ui.showDir() @@ -204,6 +217,10 @@ func (ui *UI) SetIgnoreDirPaths(paths []string) { } } +func (ui *UI) rescanDir() { + ui.AnalyzePath(ui.currentDirPath, ui.analyzer, ui.currentDir.Parent) +} + // ShouldDirBeIgnored returns true if given path should be ignored func (ui *UI) ShouldDirBeIgnored(path string) bool { return ui.ignoreDirPaths[path] @@ -297,7 +314,7 @@ func (ui *UI) deviceItemSelected(row, column int) { } } - ui.AnalyzePath(selectedDevice.MountPoint, ui.analyzer) + ui.AnalyzePath(selectedDevice.MountPoint, ui.analyzer, nil) } func (ui *UI) confirmDeletion() { @@ -393,6 +410,12 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey { ui.deleteSelected() } break + case 'r': + if ui.currentDir == nil { + break + } + ui.rescanDir() + break case 's': ui.setSorting("size") break diff --git a/cli/cli_test.go b/cli/cli_test.go index ee0ef3fd0..f8f0d11a0 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -99,7 +99,7 @@ func TestDeleteDir(t *testing.T) { ui := CreateUI(simScreen, true) ui.askBeforeDelete = false - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -129,7 +129,7 @@ func TestDeleteDirWithConfirm(t *testing.T) { ui := CreateUI(simScreen, false) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -161,7 +161,7 @@ func TestShowConfirm(t *testing.T) { ui := CreateUI(simScreen, true) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -181,6 +181,35 @@ func TestShowConfirm(t *testing.T) { assert.FileExists(t, "test_dir/nested/file2") } +func TestRescan(t *testing.T) { + fin := analyze.CreateTestDir() + defer fin() + + simScreen := tcell.NewSimulationScreen("UTF-8") + simScreen.Init() + simScreen.SetSize(50, 50) + + ui := CreateUI(simScreen, true) + + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) + + go func() { + time.Sleep(100 * time.Millisecond) + simScreen.InjectKey(tcell.KeyEnter, '1', 1) + time.Sleep(10 * time.Millisecond) + + // rescan subdir + simScreen.InjectKey(tcell.KeyRune, 'r', 1) + time.Sleep(100 * time.Millisecond) + + time.Sleep(10 * time.Millisecond) + simScreen.InjectKey(tcell.KeyRune, 'q', 1) + time.Sleep(10 * time.Millisecond) + }() + + ui.StartUILoop() +} + func TestShowDevices(t *testing.T) { if runtime.GOOS != "linux" { return @@ -262,7 +291,7 @@ func TestKeys(t *testing.T) { ui := CreateUI(simScreen, false) ui.askBeforeDelete = false - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -304,7 +333,7 @@ func TestSetIgnoreDirPaths(t *testing.T) { path, _ := filepath.Abs("test_dir/nested/subnested") ui.SetIgnoreDirPaths([]string{path}) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) diff --git a/cli/sort_test.go b/cli/sort_test.go index 91afc4fb3..e0a870aa5 100644 --- a/cli/sort_test.go +++ b/cli/sort_test.go @@ -19,7 +19,7 @@ func TestSortBySizeAsc(t *testing.T) { ui := CreateUI(simScreen, true) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -50,7 +50,7 @@ func TestSortByName(t *testing.T) { ui := CreateUI(simScreen, false) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -81,7 +81,7 @@ func TestSortByNameDesc(t *testing.T) { ui := CreateUI(simScreen, false) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -114,7 +114,7 @@ func TestSortByItemCount(t *testing.T) { ui := CreateUI(simScreen, true) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) @@ -145,7 +145,7 @@ func TestSortByItemCountDesc(t *testing.T) { ui := CreateUI(simScreen, false) - ui.AnalyzePath("test_dir", analyze.ProcessDir) + ui.AnalyzePath("test_dir", analyze.ProcessDir, nil) go func() { time.Sleep(100 * time.Millisecond) diff --git a/main.go b/main.go index f2e414da3..26d3e1de8 100644 --- a/main.go +++ b/main.go @@ -53,12 +53,12 @@ func main() { args := flag.Args() if len(args) == 1 { - ui.AnalyzePath(args[0], analyze.ProcessDir) + ui.AnalyzePath(args[0], analyze.ProcessDir, nil) } else { if runtime.GOOS == "linux" { ui.ListDevices() } else { - ui.AnalyzePath(".", analyze.ProcessDir) + ui.AnalyzePath(".", analyze.ProcessDir, nil) } }