diff --git a/cmd/ncdu/ncdu.go b/cmd/ncdu/ncdu.go index 2fd26fd6f..9d89ae237 100644 --- a/cmd/ncdu/ncdu.go +++ b/cmd/ncdu/ncdu.go @@ -89,11 +89,12 @@ func helpText() (tr []string) { " ↑,↓ or k,j to Move", " →,l to enter", " ←,h to return", - " c toggle counts", " g toggle graph", + " c toggle counts", " a toggle average size in directory", + " m toggle modified time", " u toggle human-readable format", - " n,s,C,A sort by name,size,count,average size", + " n,s,C,A,M sort by name,size,count,asize,mtime", " d delete file/directory", " v select file/directory", " V enter visual select mode", @@ -131,12 +132,14 @@ type UI struct { showGraph bool // toggle showing graph showCounts bool // toggle showing counts showDirAverageSize bool // toggle average size + showModTime bool // toggle showing timestamps humanReadable bool // toggle human-readable format visualSelectMode bool // toggle visual selection mode - sortByName int8 // +1 for normal, 0 for off, -1 for reverse - sortBySize int8 + sortByName int8 // +1 for normal (lexical), 0 for off, -1 for reverse + sortBySize int8 // +1 for normal (largest first), 0 for off, -1 for reverse (smallest first) sortByCount int8 sortByAverageSize int8 + sortByModTime int8 // +1 for normal (newest first), 0 for off, -1 for reverse (oldest first) dirPosMap map[string]dirPos // store for directory positions selectedEntries map[string]dirPos // selected entries of current directory } @@ -332,6 +335,7 @@ func (u *UI) hasEmptyDir() bool { // Draw the current screen func (u *UI) Draw() error { + ctx := context.Background() w, h := termbox.Size() u.dirListHeight = h - 3 @@ -365,7 +369,13 @@ func (u *UI) Draw() error { if y >= h-1 { break } - attrs, err := u.d.AttrI(u.sortPerm[n]) + var attrs scan.Attrs + var err error + if u.showModTime { + attrs, err = u.d.AttrWithModTimeI(ctx, u.sortPerm[n]) + } else { + attrs, err = u.d.AttrI(u.sortPerm[n]) + } _, isSelected := u.selectedEntries[entry.String()] fg := termbox.ColorWhite if attrs.EntriesHaveErrors { @@ -421,6 +431,9 @@ func (u *UI) Draw() error { extras += strings.Repeat(" ", len(ss)) } } + if u.showModTime { + extras += attrs.ModTime.Local().Format("2006-01-02 15:04:05") + " " + } if showEmptyDir { if attrs.IsDir && attrs.Count == 0 && fileFlag == ' ' { fileFlag = 'e' @@ -656,8 +669,15 @@ type ncduSort struct { // Less is part of sort.Interface. func (ds *ncduSort) Less(i, j int) bool { var iAvgSize, jAvgSize float64 - iattrs, _ := ds.d.AttrI(ds.sortPerm[i]) - jattrs, _ := ds.d.AttrI(ds.sortPerm[j]) + var iattrs, jattrs scan.Attrs + if ds.u.sortByModTime != 0 { + ctx := context.Background() + iattrs, _ = ds.d.AttrWithModTimeI(ctx, ds.sortPerm[i]) + jattrs, _ = ds.d.AttrWithModTimeI(ctx, ds.sortPerm[j]) + } else { + iattrs, _ = ds.d.AttrI(ds.sortPerm[i]) + jattrs, _ = ds.d.AttrI(ds.sortPerm[j]) + } iname, jname := ds.entries[ds.sortPerm[i]].Remote(), ds.entries[ds.sortPerm[j]].Remote() if iattrs.Count > 0 { iAvgSize = iattrs.AverageSize() @@ -679,6 +699,14 @@ func (ds *ncduSort) Less(i, j int) bool { if iattrs.Size != jattrs.Size { return iattrs.Size > jattrs.Size } + case ds.u.sortByModTime < 0: + if iattrs.ModTime != jattrs.ModTime { + return iattrs.ModTime.Before(jattrs.ModTime) + } + case ds.u.sortByModTime > 0: + if iattrs.ModTime != jattrs.ModTime { + return iattrs.ModTime.After(jattrs.ModTime) + } case ds.u.sortByCount < 0: if iattrs.Count != jattrs.Count { return iattrs.Count < jattrs.Count @@ -847,8 +875,9 @@ func NewUI(f fs.Fs) *UI { showCounts: false, showDirAverageSize: false, humanReadable: true, - sortByName: 0, // +1 for normal, 0 for off, -1 for reverse - sortBySize: 1, + sortByName: 0, + sortBySize: 1, // Sort by largest first + sortByModTime: 0, sortByCount: 0, dirPosMap: make(map[string]dirPos), selectedEntries: make(map[string]dirPos), @@ -937,6 +966,8 @@ outer: u.enter() case 'c': u.showCounts = !u.showCounts + case 'm': + u.showModTime = !u.showModTime case 'g': u.showGraph = !u.showGraph case 'a': @@ -945,6 +976,8 @@ outer: u.toggleSort(&u.sortByName) case 's': u.toggleSort(&u.sortBySize) + case 'M': + u.toggleSort(&u.sortByModTime) case 'v': u.toggleSelectForCursor() case 'V': diff --git a/cmd/ncdu/scan/scan.go b/cmd/ncdu/scan/scan.go index 3b391ff53..fb47b335c 100644 --- a/cmd/ncdu/scan/scan.go +++ b/cmd/ncdu/scan/scan.go @@ -6,6 +6,7 @@ import ( "fmt" "path" "sync" + "time" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/walk" @@ -31,6 +32,7 @@ type Dir struct { // in the total count. They are not included in the size, i.e. treated // as empty files, which means the size may be underestimated. type Attrs struct { + ModTime time.Time Size int64 Count int64 CountUnknownSize int64 @@ -193,20 +195,33 @@ func (d *Dir) Attr() (size int64, count int64) { return d.size, d.count } +// attrI returns the size, count and flags for the i-th directory entry +func (d *Dir) attrI(i int) (attrs Attrs, err error) { + subDir, isDir := d.getDir(i) + if !isDir { + return Attrs{time.Time{}, d.entries[i].Size(), 0, 0, false, true, d.entriesHaveErrors}, d.readError + } + if subDir == nil { + return Attrs{time.Time{}, 0, 0, 0, true, false, false}, nil + } + size, count := subDir.Attr() + return Attrs{time.Time{}, size, count, subDir.countUnknownSize, true, true, subDir.entriesHaveErrors}, subDir.readError +} + // AttrI returns the size, count and flags for the i-th directory entry func (d *Dir) AttrI(i int) (attrs Attrs, err error) { d.mu.Lock() defer d.mu.Unlock() - subDir, isDir := d.getDir(i) + return d.attrI(i) +} - if !isDir { - return Attrs{d.entries[i].Size(), 0, 0, false, true, d.entriesHaveErrors}, d.readError - } - if subDir == nil { - return Attrs{0, 0, 0, true, false, false}, nil - } - size, count := subDir.Attr() - return Attrs{size, count, subDir.countUnknownSize, true, true, subDir.entriesHaveErrors}, subDir.readError +// AttrWithModTimeI returns the modtime, size, count and flags for the i-th directory entry +func (d *Dir) AttrWithModTimeI(ctx context.Context, i int) (attrs Attrs, err error) { + d.mu.Lock() + defer d.mu.Unlock() + attrs, err = d.attrI(i) + attrs.ModTime = d.entries[i].ModTime(ctx) + return } // Scan the Fs passed in, returning a root directory channel and an