summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Milde <daniel@milde.cz>2021-11-07 23:08:47 +0100
committerDaniel Milde <daniel@milde.cz>2021-11-07 23:08:47 +0100
commit8edb037d8365744a8d8ac9a27f83ceee04bf16dd (patch)
treed24d13a513ce81dfb932097755110a3bd8d990b9
parent4dfbeded97cb39619896d4d1fb5e4b795b8962ee (diff)
show info about hard-linked files
closes #95
-rw-r--r--internal/testanalyze/analyze.go13
-rw-r--r--pkg/analyze/dir.go3
-rw-r--r--pkg/analyze/dir_test.go28
-rw-r--r--pkg/analyze/file.go38
-rw-r--r--pkg/analyze/file_test.go10
-rw-r--r--report/export.go1
-rw-r--r--stdout/stdout.go4
-rw-r--r--stdout/stdout_test.go2
-rw-r--r--tui/actions.go20
-rw-r--r--tui/actions_test.go41
-rw-r--r--tui/exec_test.go2
-rw-r--r--tui/sort_test.go16
-rw-r--r--tui/tui.go2
13 files changed, 127 insertions, 53 deletions
diff --git a/internal/testanalyze/analyze.go b/internal/testanalyze/analyze.go
index ac38372..76e01df 100644
--- a/internal/testanalyze/analyze.go
+++ b/internal/testanalyze/analyze.go
@@ -22,7 +22,7 @@ func (a *MockedAnalyzer) AnalyzeDir(path string, ignore analyze.ShouldDirBeIgnor
BasePath: ".",
ItemCount: 12,
}
- file := &analyze.Dir{
+ dir2 := &analyze.Dir{
File: &analyze.File{
Name: "aaa",
Usage: 1e12 + 1,
@@ -30,9 +30,8 @@ func (a *MockedAnalyzer) AnalyzeDir(path string, ignore analyze.ShouldDirBeIgnor
Mtime: time.Date(2021, 8, 27, 22, 23, 27, 0, time.UTC),
Parent: dir,
},
- ItemCount: 5,
}
- file2 := &analyze.Dir{
+ dir3 := &analyze.Dir{
File: &analyze.File{
Name: "bbb",
Usage: 1e9 + 1,
@@ -40,9 +39,8 @@ func (a *MockedAnalyzer) AnalyzeDir(path string, ignore analyze.ShouldDirBeIgnor
Mtime: time.Date(2021, 8, 27, 22, 23, 26, 0, time.UTC),
Parent: dir,
},
- ItemCount: 3,
}
- file3 := &analyze.Dir{
+ dir4 := &analyze.Dir{
File: &analyze.File{
Name: "ccc",
Usage: 1e6 + 1,
@@ -50,16 +48,15 @@ func (a *MockedAnalyzer) AnalyzeDir(path string, ignore analyze.ShouldDirBeIgnor
Mtime: time.Date(2021, 8, 27, 22, 23, 25, 0, time.UTC),
Parent: dir,
},
- ItemCount: 2,
}
- file4 := &analyze.File{
+ file := &analyze.File{
Name: "ddd",
Usage: 1e3 + 1,
Size: 1e3 + 2,
Mtime: time.Date(2021, 8, 27, 22, 23, 24, 0, time.UTC),
Parent: dir,
}
- dir.Files = analyze.Files{file, file2, file3, file4}
+ dir.Files = analyze.Files{dir2, dir3, dir4, file}
return dir
}
diff --git a/pkg/analyze/dir.go b/pkg/analyze/dir.go
index 387549d..f91ddda 100644
--- a/pkg/analyze/dir.go
+++ b/pkg/analyze/dir.go
@@ -79,9 +79,6 @@ func (a *ParallelAnalyzer) AnalyzeDir(path string, ignore ShouldDirBeIgnored) *D
dir.BasePath = filepath.Dir(path)
a.wait.Wait()
- links := make(AlreadyCountedHardlinks, 10)
- dir.UpdateStats(links)
-
a.doneChan <- struct{}{} // finish updateProgress here
a.doneChan <- struct{}{} // and there
diff --git a/pkg/analyze/dir_test.go b/pkg/analyze/dir_test.go
index 7bd282d..874de8c 100644
--- a/pkg/analyze/dir_test.go
+++ b/pkg/analyze/dir_test.go
@@ -22,13 +22,12 @@ func TestAnalyzeDir(t *testing.T) {
analyzer := CreateAnalyzer()
dir := analyzer.AnalyzeDir("test_dir", func(_, _ string) bool { return false })
- c := analyzer.GetProgressChan()
- progress := <-c
+ progress := <-analyzer.GetProgressChan()
assert.GreaterOrEqual(t, progress.TotalSize, int64(0))
analyzer.ResetProgress()
- done := analyzer.GetDoneChan()
- <-done
+ <-analyzer.GetDoneChan()
+ dir.UpdateStats(make(HardLinkedItems))
// test dir info
assert.Equal(t, "test_dir", dir.Name)
@@ -71,7 +70,11 @@ func TestFlags(t *testing.T) {
err = os.Symlink("test_dir/nested/file2", "test_dir/nested/file3")
assert.Nil(t, err)
- dir := CreateAnalyzer().AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ analyzer := CreateAnalyzer()
+ dir := analyzer.AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ <-analyzer.GetDoneChan()
+ dir.UpdateStats(make(HardLinkedItems))
+
sort.Sort(dir.Files)
assert.Equal(t, int64(28+4096*4), dir.Size)
@@ -93,7 +96,10 @@ func TestHardlink(t *testing.T) {
err := os.Link("test_dir/nested/file2", "test_dir/nested/file3")
assert.Nil(t, err)
- dir := CreateAnalyzer().AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ analyzer := CreateAnalyzer()
+ dir := analyzer.AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ <-analyzer.GetDoneChan()
+ dir.UpdateStats(make(HardLinkedItems))
assert.Equal(t, int64(7+4096*3), dir.Size) // file2 and file3 are counted just once for size
assert.Equal(t, 6, dir.ItemCount) // but twice for item count
@@ -115,7 +121,10 @@ func TestErr(t *testing.T) {
assert.Nil(t, err)
}()
- dir := CreateAnalyzer().AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ analyzer := CreateAnalyzer()
+ dir := analyzer.AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ <-analyzer.GetDoneChan()
+ dir.UpdateStats(make(HardLinkedItems))
assert.Equal(t, "test_dir", dir.GetName())
assert.Equal(t, 2, dir.ItemCount)
@@ -131,5 +140,8 @@ func BenchmarkAnalyzeDir(b *testing.B) {
b.ResetTimer()
- CreateAnalyzer().AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ analyzer := CreateAnalyzer()
+ dir := analyzer.AnalyzeDir("test_dir", func(_, _ string) bool { return false })
+ <-analyzer.GetDoneChan()
+ dir.UpdateStats(make(HardLinkedItems))
}
diff --git a/pkg/analyze/file.go b/pkg/analyze/file.go
index c07e382..209ce2c 100644
--- a/pkg/analyze/file.go
+++ b/pkg/analyze/file.go
@@ -7,8 +7,8 @@ import (
"time"
)
-// AlreadyCountedHardlinks holds all files with hardlinks that have already been counted
-type AlreadyCountedHardlinks map[uint64]bool
+// HardLinkedItems maps inode number to array of all hard linked items
+type HardLinkedItems map[uint64]Files
// Item is fs item (file or dir)
type Item interface {
@@ -22,8 +22,9 @@ type Item interface {
GetMtime() time.Time
GetItemCount() int
GetParent() *Dir
+ GetMultiLinkedInode() uint64
EncodeJSON(writer io.Writer, topLevel bool) error
- getItemStats(links AlreadyCountedHardlinks) (int, int64, int64)
+ getItemStats(linkedItems HardLinkedItems) (int, int64, int64)
}
// File struct
@@ -91,21 +92,26 @@ func (f *File) GetItemCount() int {
return 1
}
-func (f *File) alreadyCounted(links AlreadyCountedHardlinks) bool {
+// GetMultiLinkedInode returns inode number of multilinked file
+func (f *File) GetMultiLinkedInode() uint64 {
+ return f.Mli
+}
+
+func (f *File) alreadyCounted(linkedItems HardLinkedItems) bool {
mli := f.Mli
+ counted := false
if mli > 0 {
- if !links[mli] {
- links[mli] = true
- return false
+ if _, ok := linkedItems[mli]; ok {
+ f.Flag = 'H'
+ counted = true
}
- f.Flag = 'H'
- return true
+ linkedItems[mli] = append(linkedItems[mli], f)
}
- return false
+ return counted
}
-func (f *File) getItemStats(links AlreadyCountedHardlinks) (int, int64, int64) {
- if f.alreadyCounted(links) {
+func (f *File) getItemStats(linkedItems HardLinkedItems) (int, int64, int64) {
+ if f.alreadyCounted(linkedItems) {
return 1, 0, 0
}
return 1, f.GetSize(), f.GetUsage()
@@ -142,18 +148,18 @@ func (f *Dir) GetPath() string {
return filepath.Join(f.Parent.GetPath(), f.Name)
}
-func (f *Dir) getItemStats(links AlreadyCountedHardlinks) (int, int64, int64) {
- f.UpdateStats(links)
+func (f *Dir) getItemStats(linkedItems HardLinkedItems) (int, int64, int64) {
+ f.UpdateStats(linkedItems)
return f.ItemCount, f.GetSize(), f.GetUsage()
}
// UpdateStats recursively updates size and item count
-func (f *Dir) UpdateStats(links AlreadyCountedHardlinks) {
+func (f *Dir) UpdateStats(linkedItems HardLinkedItems) {
totalSize := int64(4096)
totalUsage := int64(4096)
var itemCount int
for _, entry := range f.Files {
- count, size, usage := entry.getItemStats(links)
+ count, size, usage := entry.getItemStats(linkedItems)
totalSize += size
totalUsage += usage
itemCount += count
diff --git a/pkg/analyze/file_test.go b/pkg/analyze/file_test.go
index b41819c..48cdc51 100644
--- a/pkg/analyze/file_test.go
+++ b/pkg/analyze/file_test.go
@@ -378,3 +378,13 @@ func TestUpdateStats(t *testing.T) {
assert.Equal(t, int64(4096+5), dir.Size)
assert.Equal(t, 42, dir.GetMtime().Minute())
}
+
+func TestGetMultiLinkedInode(t *testing.T) {
+ file := &File{
+ Name: "xxx",
+ Mli: 5,
+ }
+
+ assert.Equal(t, uint64(5), file.GetMultiLinkedInode())
+
+}
diff --git a/report/export.go b/report/export.go
index 0d9f226..432e921 100644
--- a/report/export.go
+++ b/report/export.go
@@ -87,6 +87,7 @@ func (ui *UI) AnalyzePath(path string, _ *analyze.Dir) error {
defer wait.Done()
defer debug.SetGCPercent(debug.SetGCPercent(-1))
dir = ui.Analyzer.AnalyzeDir(path, ui.CreateIgnoreFunc())
+ dir.UpdateStats(make(analyze.HardLinkedItems, 10))
}()
wait.Wait()
diff --git a/stdout/stdout.go b/stdout/stdout.go
index ab627c5..9be08eb 100644
--- a/stdout/stdout.go
+++ b/stdout/stdout.go
@@ -142,6 +142,7 @@ func (ui *UI) AnalyzePath(path string, _ *analyze.Dir) error {
defer wait.Done()
defer debug.SetGCPercent(debug.SetGCPercent(-1))
dir = ui.Analyzer.AnalyzeDir(path, ui.CreateIgnoreFunc())
+ dir.UpdateStats(make(analyze.HardLinkedItems, 10))
}()
wait.Wait()
@@ -246,8 +247,7 @@ func (ui *UI) ReadAnalysis(input io.Reader) error {
}
runtime.GC()
- links := make(analyze.AlreadyCountedHardlinks, 10)
- dir.UpdateStats(links)
+ dir.UpdateStats(make(analyze.HardLinkedItems, 10))
if ui.ShowProgress {
doneChan <- struct{}{}
diff --git a/stdout/stdout_test.go b/stdout/stdout_test.go
index 89a86fc..5441441 100644
--- a/stdout/stdout_test.go
+++ b/stdout/stdout_test.go
@@ -109,8 +109,6 @@ func TestItemRows(t *testing.T) {
err := ui.AnalyzePath("test_dir", nil)
assert.Nil(t, err)
- assert.Contains(t, output.String(), "GiB")
- assert.Contains(t, output.String(), "MiB")
assert.Contains(t, output.String(), "KiB")
}
diff --git a/tui/actions.go b/tui/actions.go
index f5b3c0a..1e82d77 100644
--- a/tui/actions.go
+++ b/tui/actions.go
@@ -62,14 +62,13 @@ func (ui *UI) AnalyzePath(path string, parentDir *analyze.Dir) error {
currentDir.Parent = parentDir
parentDir.Files = parentDir.Files.RemoveByName(currentDir.Name)
parentDir.Files.Append(currentDir)
-
- links := make(analyze.AlreadyCountedHardlinks, 10)
- ui.topDir.UpdateStats(links)
} else {
ui.topDirPath = path
ui.topDir = currentDir
}
+ ui.topDir.UpdateStats(ui.linkedItems)
+
ui.app.QueueUpdateDraw(func() {
ui.currentDir = currentDir
ui.showDir()
@@ -119,7 +118,7 @@ func (ui *UI) ReadAnalysis(input io.Reader) error {
ui.topDirPath = ui.currentDir.GetPath()
ui.topDir = ui.currentDir
- links := make(analyze.AlreadyCountedHardlinks, 10)
+ links := make(analyze.HardLinkedItems, 10)
ui.topDir.UpdateStats(links)
ui.app.QueueUpdateDraw(func() {
@@ -303,6 +302,8 @@ func (ui *UI) showInfo() {
numberColor = "[::b]"
}
+ linesCount := 12
+
text := tview.NewTextView().SetDynamicColors(true)
text.SetBorder(true).SetBorderPadding(2, 2, 2, 2)
text.SetBorderColor(tcell.ColorDefault)
@@ -320,13 +321,22 @@ func (ui *UI) showInfo() {
content += numberColor + ui.formatSize(selectedFile.GetSize(), false, true)
content += fmt.Sprintf(" (%s%d[-::] B)", numberColor, selectedFile.GetSize()) + "\n"
+ if selectedFile.GetMultiLinkedInode() > 0 {
+ linkedItems := ui.linkedItems[selectedFile.GetMultiLinkedInode()]
+ linesCount += 2 + len(linkedItems)
+ content += "\nHard-linked files:\n"
+ for _, linkedItem := range linkedItems {
+ content += "\t" + linkedItem.GetPath() + "\n"
+ }
+ }
+
text.SetText(content)
flex := tview.NewFlex().
AddItem(nil, 0, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(nil, 0, 1, false).
- AddItem(text, 13, 1, false).
+ AddItem(text, linesCount, 1, false).
AddItem(nil, 0, 1, false), 80, 1, false).
AddItem(nil, 0, 1, false)
diff --git a/tui/actions_test.go b/tui/actions_test.go
index 5fca310..c4880a1 100644
--- a/tui/actions_test.go
+++ b/tui/actions_test.go
@@ -330,6 +330,47 @@ func TestShowInfoBW(t *testing.T) {
assert.True(t, ui.pages.HasPage("info"))
}
+func TestShowInfoWithHardlinks(t *testing.T) {
+ fin := testdir.CreateTestDir()
+ defer fin()
+ simScreen := testapp.CreateSimScreen(50, 50)
+ defer simScreen.Fini()
+
+ app := testapp.CreateMockedApp(true)
+ ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true)
+ ui.done = make(chan struct{})
+ err := ui.AnalyzePath("test_dir", nil)
+ assert.Nil(t, err)
+
+ <-ui.done // wait for analyzer
+
+ for _, f := range ui.app.(*testapp.MockedApp).UpdateDraws {
+ f()
+ }
+
+ nested := ui.currentDir.Files[0].(*analyze.Dir)
+ subnested := nested.Files[1].(*analyze.Dir)
+ file := subnested.Files[0].(*analyze.File)
+ file2 := nested.Files[0].(*analyze.File)
+ file.Mli = 1
+ file2.Mli = 1
+
+ ui.currentDir.UpdateStats(ui.linkedItems)
+
+ assert.Equal(t, "test_dir", ui.currentDir.Name)
+
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRight, 'l', 0))
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'i', 0))
+ ui.table.Select(2, 0)
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'i', 0))
+
+ assert.True(t, ui.pages.HasPage("info"))
+
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, 'q', 0))
+
+ assert.False(t, ui.pages.HasPage("info"))
+}
+
func TestShowInfoWithoutCurrentDir(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
diff --git a/tui/exec_test.go b/tui/exec_test.go
index 6588da9..72393ae 100644
--- a/tui/exec_test.go
+++ b/tui/exec_test.go
@@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestExec(t *testing.T) {
+func TestExecute(t *testing.T) {
err := Execute("true", []string{}, []string{})
assert.Nil(t, err)
diff --git a/tui/sort_test.go b/tui/sort_test.go
index 43e4568..3257f1a 100644
--- a/tui/sort_test.go
+++ b/tui/sort_test.go
@@ -24,9 +24,9 @@ func TestSortByApparentSizeAsc(t *testing.T) {
assert.Equal(t, 4, ui.table.GetRowCount())
assert.Contains(t, ui.table.GetCell(0, 0).Text, "ddd")
- assert.Contains(t, ui.table.GetCell(1, 0).Text, "ccc")
+ assert.Contains(t, ui.table.GetCell(1, 0).Text, "aaa")
assert.Contains(t, ui.table.GetCell(2, 0).Text, "bbb")
- assert.Contains(t, ui.table.GetCell(3, 0).Text, "aaa")
+ assert.Contains(t, ui.table.GetCell(3, 0).Text, "ccc")
}
func TestAnalyzeBySize(t *testing.T) {
@@ -44,9 +44,9 @@ func TestSortBySizeAsc(t *testing.T) {
assert.Equal(t, 4, ui.table.GetRowCount())
assert.Contains(t, ui.table.GetCell(0, 0).Text, "ddd")
- assert.Contains(t, ui.table.GetCell(1, 0).Text, "ccc")
+ assert.Contains(t, ui.table.GetCell(1, 0).Text, "aaa")
assert.Contains(t, ui.table.GetCell(2, 0).Text, "bbb")
- assert.Contains(t, ui.table.GetCell(3, 0).Text, "aaa")
+ assert.Contains(t, ui.table.GetCell(3, 0).Text, "ccc")
}
func TestAnalyzeByName(t *testing.T) {
@@ -83,10 +83,10 @@ func TestAnalyzeByItemCountAsc(t *testing.T) {
ui := getAnalyzedPathWithSorting("itemCount", "asc", false)
assert.Equal(t, 4, ui.table.GetRowCount())
- assert.Contains(t, ui.table.GetCell(0, 0).Text, "ddd")
- assert.Contains(t, ui.table.GetCell(1, 0).Text, "ccc")
- assert.Contains(t, ui.table.GetCell(2, 0).Text, "bbb")
- assert.Contains(t, ui.table.GetCell(3, 0).Text, "aaa")
+ assert.Contains(t, ui.table.GetCell(0, 0).Text, "aaa")
+ assert.Contains(t, ui.table.GetCell(1, 0).Text, "bbb")
+ assert.Contains(t, ui.table.GetCell(2, 0).Text, "ccc")
+ assert.Contains(t, ui.table.GetCell(3, 0).Text, "ddd")
}
func TestAnalyzeByMtime(t *testing.T) {
diff --git a/tui/tui.go b/tui/tui.go
index d0dc5d2..4fb5b90 100644
--- a/tui/tui.go
+++ b/tui/tui.go
@@ -69,6 +69,7 @@ type UI struct {
remover func(*analyze.Dir, analyze.Item) error
emptier func(*analyze.Dir, analyze.Item) error
exec func(argv0 string, argv []string, envv []string) error
+ linkedItems analyze.HardLinkedItems
}
// CreateUI creates the whole UI app
@@ -87,6 +88,7 @@ func CreateUI(app common.TermApplication, screen tcell.Screen, output io.Writer,
remover: analyze.RemoveItemFromDir,
emptier: analyze.EmptyFileFromDir,
exec: Execute,
+ linkedItems: make(analyze.HardLinkedItems, 10),
}
ui.resetSorting()