Skip to content

Commit

Permalink
show info about hard-linked files
Browse files Browse the repository at this point in the history
closes dundee#95
  • Loading branch information
dundee committed Nov 7, 2021
1 parent 4dfbede commit 8edb037
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 53 deletions.
13 changes: 5 additions & 8 deletions internal/testanalyze/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,41 @@ 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,
Size: 1e12 + 2,
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,
Size: 1e9 + 2,
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,
Size: 1e6 + 2,
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
}
Expand Down
3 changes: 0 additions & 3 deletions pkg/analyze/dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 20 additions & 8 deletions pkg/analyze/dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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))
}
38 changes: 22 additions & 16 deletions pkg/analyze/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions pkg/analyze/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

}
1 change: 1 addition & 0 deletions report/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions stdout/stdout.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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{}{}
Expand Down
2 changes: 0 additions & 2 deletions stdout/stdout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
20 changes: 15 additions & 5 deletions tui/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand Down
41 changes: 41 additions & 0 deletions tui/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tui/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 8edb037

Please sign in to comment.