diff options
-rw-r--r-- | analyze/dir.go | 79 | ||||
-rw-r--r-- | analyze/dir_test.go | 8 | ||||
-rw-r--r-- | internal/testanalyze/analyze.go | 17 | ||||
-rw-r--r-- | internal/testapp/app.go | 5 | ||||
-rw-r--r-- | stdout/stdout.go | 12 | ||||
-rw-r--r-- | tui/tui.go | 34 | ||||
-rw-r--r-- | tui/tui_test.go | 5 |
7 files changed, 97 insertions, 63 deletions
diff --git a/analyze/dir.go b/analyze/dir.go index fbbc1c7..3ec8b0f 100644 --- a/analyze/dir.go +++ b/analyze/dir.go @@ -10,11 +10,10 @@ import ( // CurrentProgress struct type CurrentProgress struct { - Mutex *sync.Mutex + mutex *sync.Mutex CurrentItemName string ItemCount int TotalSize int64 - Done bool } var concurrencyLimit chan struct{} = make(chan struct{}, 3*runtime.GOMAXPROCS(0)) @@ -25,40 +24,49 @@ type ShouldDirBeIgnored func(path string) bool // Analyzer is type for dir analyzing function type Analyzer interface { AnalyzeDir(path string, ignore ShouldDirBeIgnored) *Dir - GetProgress() *CurrentProgress + GetProgressChan() chan CurrentProgress + GetDoneChan() chan struct{} ResetProgress() } // ParallelAnalyzer implements Analyzer type ParallelAnalyzer struct { - progress *CurrentProgress - wait sync.WaitGroup - ignoreDir ShouldDirBeIgnored + progress *CurrentProgress + progressChan chan CurrentProgress + doneChan chan struct{} + wait sync.WaitGroup + ignoreDir ShouldDirBeIgnored } // CreateAnalyzer returns Analyzer func CreateAnalyzer() Analyzer { return &ParallelAnalyzer{ progress: &CurrentProgress{ - Mutex: &sync.Mutex{}, - Done: false, + mutex: &sync.Mutex{}, ItemCount: 0, TotalSize: int64(0), }, + progressChan: make(chan CurrentProgress, 10), + doneChan: make(chan struct{}, 1), } } -// GetProgress returns progress -func (a *ParallelAnalyzer) GetProgress() *CurrentProgress { - return a.progress +// GetProgressChan returns channel for getting progress +func (a *ParallelAnalyzer) GetProgressChan() chan CurrentProgress { + return a.progressChan +} + +// GetDoneChan returns channel for checking when analysis is done +func (a *ParallelAnalyzer) GetDoneChan() chan struct{} { + return a.doneChan } // ResetProgress returns progress func (a *ParallelAnalyzer) ResetProgress() { - a.progress.Done = false a.progress.ItemCount = 0 a.progress.TotalSize = int64(0) - a.progress.Mutex = &sync.Mutex{} + a.progress.CurrentItemName = "" + a.progress.mutex = &sync.Mutex{} } // AnalyzeDir analyzes given path @@ -71,20 +79,19 @@ func (a *ParallelAnalyzer) AnalyzeDir(path string, ignore ShouldDirBeIgnored) *D links := make(AlreadyCountedHardlinks, 10) dir.UpdateStats(links) - a.progress.Mutex.Lock() - a.progress.Done = true - a.progress.Mutex.Unlock() + a.doneChan <- struct{}{} return dir } func (a *ParallelAnalyzer) processDir(path string) *Dir { var ( - file *File - err error - mutex sync.Mutex - totalSize int64 - info os.FileInfo + file *File + err error + totalSize int64 + info os.FileInfo + subDirChan chan *Dir = make(chan *Dir) + dirCount int = 0 ) files, err := os.ReadDir(path) @@ -107,19 +114,15 @@ func (a *ParallelAnalyzer) processDir(path string) *Dir { if a.ignoreDir(entryPath) { continue } + dirCount += 1 - a.wait.Add(1) go func() { concurrencyLimit <- struct{}{} subdir := a.processDir(entryPath) subdir.Parent = dir - mutex.Lock() - dir.Files.Append(subdir) - mutex.Unlock() - + subDirChan <- subdir <-concurrencyLimit - a.wait.Done() }() } else { info, err = f.Info() @@ -137,22 +140,36 @@ func (a *ParallelAnalyzer) processDir(path string) *Dir { totalSize += info.Size() - mutex.Lock() dir.Files.Append(file) - mutex.Unlock() } } + a.wait.Add(1) + go func() { + var sub *Dir + + for i := 0; i < dirCount; i++ { + sub = <-subDirChan + dir.Files.Append(sub) + } + + a.wait.Done() + }() + a.updateProgress(path, len(files), totalSize) return dir } func (a *ParallelAnalyzer) updateProgress(path string, itemCount int, totalSize int64) { - a.progress.Mutex.Lock() + a.progress.mutex.Lock() a.progress.CurrentItemName = path a.progress.ItemCount += itemCount a.progress.TotalSize += totalSize - a.progress.Mutex.Unlock() + select { + case a.progressChan <- *a.progress: + default: + } + a.progress.mutex.Unlock() } func getDirFlag(err error, items int) rune { diff --git a/analyze/dir_test.go b/analyze/dir_test.go index e576793..93a0834 100644 --- a/analyze/dir_test.go +++ b/analyze/dir_test.go @@ -16,9 +16,13 @@ func TestAnalyzeDir(t *testing.T) { analyzer := CreateAnalyzer() dir := analyzer.AnalyzeDir("test_dir", func(_ string) bool { return false }) - assert.True(t, analyzer.GetProgress().Done) + c := analyzer.GetProgressChan() + progress := <-c + assert.Equal(t, "test_dir", progress.CurrentItemName) analyzer.ResetProgress() - assert.False(t, analyzer.GetProgress().Done) + + done := analyzer.GetDoneChan() + <-done // test dir info assert.Equal(t, "test_dir", dir.Name) diff --git a/internal/testanalyze/analyze.go b/internal/testanalyze/analyze.go index caadf6b..508ff43 100644 --- a/internal/testanalyze/analyze.go +++ b/internal/testanalyze/analyze.go @@ -2,7 +2,6 @@ package testanalyze import ( "errors" - "sync" "github.com/dundee/gdu/v4/analyze" ) @@ -59,12 +58,16 @@ func (a *MockedAnalyzer) AnalyzeDir(path string, ignore analyze.ShouldDirBeIgnor return dir } -// GetProgress returns always Done -func (a *MockedAnalyzer) GetProgress() *analyze.CurrentProgress { - return &analyze.CurrentProgress{ - Done: true, - Mutex: &sync.Mutex{}, - } +// GetProgressChan returns always Done +func (a *MockedAnalyzer) GetProgressChan() chan analyze.CurrentProgress { + return make(chan analyze.CurrentProgress) +} + +// GetDoneChan returns always Done +func (a *MockedAnalyzer) GetDoneChan() chan struct{} { + c := make(chan struct{}, 1) + defer func() { c <- struct{}{} }() + return c } // ResetProgress does nothing diff --git a/internal/testapp/app.go b/internal/testapp/app.go index 04e026c..9cc2309 100644 --- a/internal/testapp/app.go +++ b/internal/testapp/app.go @@ -2,6 +2,7 @@ package testapp import ( "errors" + "sync" "github.com/dundee/gdu/v4/common" "github.com/gdamore/tcell/v2" @@ -25,6 +26,7 @@ type MockedApp struct { FailRun bool UpdateDraws []func() BeforeDraws []func(screen tcell.Screen) bool + mutex *sync.Mutex } // CreateMockedApp returns app with simulation screen for tests @@ -33,6 +35,7 @@ func CreateMockedApp(failRun bool) common.TermApplication { FailRun: failRun, UpdateDraws: make([]func(), 0, 1), BeforeDraws: make([]func(screen tcell.Screen) bool, 0, 1), + mutex: &sync.Mutex{}, } return app } @@ -66,7 +69,9 @@ func (app *MockedApp) SetInputCapture(capture func(event *tcell.EventKey) *tcell // QueueUpdateDraw does nothing func (app *MockedApp) QueueUpdateDraw(f func()) *tview.Application { + app.mutex.Lock() app.UpdateDraws = append(app.UpdateDraws, f) + app.mutex.Unlock() return nil } diff --git a/stdout/stdout.go b/stdout/stdout.go index 647e243..8d728fe 100644 --- a/stdout/stdout.go +++ b/stdout/stdout.go @@ -202,15 +202,18 @@ func (ui *UI) updateProgress() { progressRunes := []rune(`⠇⠏⠋⠙⠹⠸⠼⠴⠦⠧`) - progress := ui.analyzer.GetProgress() + progressChan := ui.analyzer.GetProgressChan() + doneChan := ui.analyzer.GetDoneChan() + + var progress analyze.CurrentProgress i := 0 for { - progress.Mutex.Lock() - fmt.Fprint(ui.output, emptyRow) - if progress.Done { + select { + case progress = <-progressChan: + case <-doneChan: fmt.Fprint(ui.output, "\r") return } @@ -221,7 +224,6 @@ func (ui *UI) updateProgress() { ui.red.Sprint(progress.ItemCount)+ " size: "+ ui.formatSize(progress.TotalSize)) - progress.Mutex.Unlock() time.Sleep(100 * time.Millisecond) i++ @@ -344,26 +344,30 @@ func (ui *UI) updateProgress() { color = "[red:black:b]" } - progress := ui.analyzer.GetProgress() + progressChan := ui.analyzer.GetProgressChan() + doneChan := ui.analyzer.GetDoneChan() - for { - progress.Mutex.Lock() + var progress analyze.CurrentProgress - if progress.Done { + for { + select { + case progress = <-progressChan: + case <-doneChan: return } - ui.app.QueueUpdateDraw(func() { - ui.progress.SetText("Total items: " + - color + - fmt.Sprint(progress.ItemCount) + - "[white:black:-] size: " + - color + - ui.formatSize(progress.TotalSize, false, false) + - "[white:black:-]\nCurrent item: [white:black:b]" + - progress.CurrentItemName) - }) - progress.Mutex.Unlock() + func(itemCount int, totalSize int64, currentItem string) { + ui.app.QueueUpdateDraw(func() { + ui.progress.SetText("Total items: " + + color + + fmt.Sprint(itemCount) + + "[white:black:-] size: " + + color + + ui.formatSize(totalSize, false, false) + + "[white:black:-]\nCurrent item: [white:black:b]" + + currentItem) + }) + }(progress.ItemCount, progress.TotalSize, progress.CurrentItemName) time.Sleep(100 * time.Millisecond) } diff --git a/tui/tui_test.go b/tui/tui_test.go index 171a682..1d15c3d 100644 --- a/tui/tui_test.go +++ b/tui/tui_test.go @@ -61,9 +61,8 @@ func TestUpdateProgress(t *testing.T) { defer simScreen.Fini() ui := CreateUI(app, false, false) - progress := ui.analyzer.GetProgress() - progress.Done = true - progress.CurrentItemName = "xxx" + done := ui.analyzer.GetDoneChan() + done <- struct{}{} ui.updateProgress() assert.True(t, true) } |