ncdu: add support for modification time

This commit is contained in:
albertony 2022-11-04 14:19:25 +01:00 committed by Nick Craig-Wood
parent 528fc899fb
commit 64cdbb67b5
2 changed files with 66 additions and 18 deletions

View File

@ -89,11 +89,12 @@ func helpText() (tr []string) {
" ↑,↓ or k,j to Move", " ↑,↓ or k,j to Move",
" →,l to enter", " →,l to enter",
" ←,h to return", " ←,h to return",
" c toggle counts",
" g toggle graph", " g toggle graph",
" c toggle counts",
" a toggle average size in directory", " a toggle average size in directory",
" m toggle modified time",
" u toggle human-readable format", " 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", " d delete file/directory",
" v select file/directory", " v select file/directory",
" V enter visual select mode", " V enter visual select mode",
@ -131,12 +132,14 @@ type UI struct {
showGraph bool // toggle showing graph showGraph bool // toggle showing graph
showCounts bool // toggle showing counts showCounts bool // toggle showing counts
showDirAverageSize bool // toggle average size showDirAverageSize bool // toggle average size
showModTime bool // toggle showing timestamps
humanReadable bool // toggle human-readable format humanReadable bool // toggle human-readable format
visualSelectMode bool // toggle visual selection mode visualSelectMode bool // toggle visual selection mode
sortByName int8 // +1 for normal, 0 for off, -1 for reverse sortByName int8 // +1 for normal (lexical), 0 for off, -1 for reverse
sortBySize int8 sortBySize int8 // +1 for normal (largest first), 0 for off, -1 for reverse (smallest first)
sortByCount int8 sortByCount int8
sortByAverageSize 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 dirPosMap map[string]dirPos // store for directory positions
selectedEntries map[string]dirPos // selected entries of current directory selectedEntries map[string]dirPos // selected entries of current directory
} }
@ -332,6 +335,7 @@ func (u *UI) hasEmptyDir() bool {
// Draw the current screen // Draw the current screen
func (u *UI) Draw() error { func (u *UI) Draw() error {
ctx := context.Background()
w, h := termbox.Size() w, h := termbox.Size()
u.dirListHeight = h - 3 u.dirListHeight = h - 3
@ -365,7 +369,13 @@ func (u *UI) Draw() error {
if y >= h-1 { if y >= h-1 {
break 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()] _, isSelected := u.selectedEntries[entry.String()]
fg := termbox.ColorWhite fg := termbox.ColorWhite
if attrs.EntriesHaveErrors { if attrs.EntriesHaveErrors {
@ -421,6 +431,9 @@ func (u *UI) Draw() error {
extras += strings.Repeat(" ", len(ss)) extras += strings.Repeat(" ", len(ss))
} }
} }
if u.showModTime {
extras += attrs.ModTime.Local().Format("2006-01-02 15:04:05") + " "
}
if showEmptyDir { if showEmptyDir {
if attrs.IsDir && attrs.Count == 0 && fileFlag == ' ' { if attrs.IsDir && attrs.Count == 0 && fileFlag == ' ' {
fileFlag = 'e' fileFlag = 'e'
@ -656,8 +669,15 @@ type ncduSort struct {
// Less is part of sort.Interface. // Less is part of sort.Interface.
func (ds *ncduSort) Less(i, j int) bool { func (ds *ncduSort) Less(i, j int) bool {
var iAvgSize, jAvgSize float64 var iAvgSize, jAvgSize float64
iattrs, _ := ds.d.AttrI(ds.sortPerm[i]) var iattrs, jattrs scan.Attrs
jattrs, _ := ds.d.AttrI(ds.sortPerm[j]) 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() iname, jname := ds.entries[ds.sortPerm[i]].Remote(), ds.entries[ds.sortPerm[j]].Remote()
if iattrs.Count > 0 { if iattrs.Count > 0 {
iAvgSize = iattrs.AverageSize() iAvgSize = iattrs.AverageSize()
@ -679,6 +699,14 @@ func (ds *ncduSort) Less(i, j int) bool {
if iattrs.Size != jattrs.Size { if iattrs.Size != jattrs.Size {
return 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: case ds.u.sortByCount < 0:
if iattrs.Count != jattrs.Count { if iattrs.Count != jattrs.Count {
return iattrs.Count < jattrs.Count return iattrs.Count < jattrs.Count
@ -847,8 +875,9 @@ func NewUI(f fs.Fs) *UI {
showCounts: false, showCounts: false,
showDirAverageSize: false, showDirAverageSize: false,
humanReadable: true, humanReadable: true,
sortByName: 0, // +1 for normal, 0 for off, -1 for reverse sortByName: 0,
sortBySize: 1, sortBySize: 1, // Sort by largest first
sortByModTime: 0,
sortByCount: 0, sortByCount: 0,
dirPosMap: make(map[string]dirPos), dirPosMap: make(map[string]dirPos),
selectedEntries: make(map[string]dirPos), selectedEntries: make(map[string]dirPos),
@ -937,6 +966,8 @@ outer:
u.enter() u.enter()
case 'c': case 'c':
u.showCounts = !u.showCounts u.showCounts = !u.showCounts
case 'm':
u.showModTime = !u.showModTime
case 'g': case 'g':
u.showGraph = !u.showGraph u.showGraph = !u.showGraph
case 'a': case 'a':
@ -945,6 +976,8 @@ outer:
u.toggleSort(&u.sortByName) u.toggleSort(&u.sortByName)
case 's': case 's':
u.toggleSort(&u.sortBySize) u.toggleSort(&u.sortBySize)
case 'M':
u.toggleSort(&u.sortByModTime)
case 'v': case 'v':
u.toggleSelectForCursor() u.toggleSelectForCursor()
case 'V': case 'V':

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"path" "path"
"sync" "sync"
"time"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/walk" "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 // in the total count. They are not included in the size, i.e. treated
// as empty files, which means the size may be underestimated. // as empty files, which means the size may be underestimated.
type Attrs struct { type Attrs struct {
ModTime time.Time
Size int64 Size int64
Count int64 Count int64
CountUnknownSize int64 CountUnknownSize int64
@ -193,20 +195,33 @@ func (d *Dir) Attr() (size int64, count int64) {
return d.size, d.count 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 // AttrI returns the size, count and flags for the i-th directory entry
func (d *Dir) AttrI(i int) (attrs Attrs, err error) { func (d *Dir) AttrI(i int) (attrs Attrs, err error) {
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() defer d.mu.Unlock()
subDir, isDir := d.getDir(i) return d.attrI(i)
}
if !isDir { // AttrWithModTimeI returns the modtime, size, count and flags for the i-th directory entry
return Attrs{d.entries[i].Size(), 0, 0, false, true, d.entriesHaveErrors}, d.readError func (d *Dir) AttrWithModTimeI(ctx context.Context, i int) (attrs Attrs, err error) {
} d.mu.Lock()
if subDir == nil { defer d.mu.Unlock()
return Attrs{0, 0, 0, true, false, false}, nil attrs, err = d.attrI(i)
} attrs.ModTime = d.entries[i].ModTime(ctx)
size, count := subDir.Attr() return
return Attrs{size, count, subDir.countUnknownSize, true, true, subDir.entriesHaveErrors}, subDir.readError
} }
// Scan the Fs passed in, returning a root directory channel and an // Scan the Fs passed in, returning a root directory channel and an