package main import ( "errors" "fmt" "os" "path/filepath" "sort" "strings" ) type FileEntry struct { path string size *uint64 modSize *uint64 fileCount *uint64 modFileCount *uint64 parent Entry removal bool noPerm bool isMount bool isExec bool } var _ Entry = &FileEntry{} func (e *FileEntry) Name() string { return filepath.Base(e.path) } func (e *FileEntry) Path() string { return e.path } func (e *FileEntry) Size() uint64 { if e.size == nil { e.Scan(nil, nil) } return *e.size } func (e *FileEntry) Entries() []Entry { return []Entry{} } func (e *FileEntry) String() string { var ic string if e.isExec { ic = icon("app") } else { ic = icon(filepath.Ext(e.Path())) } if e.removal { return ColorRed.Sprintf( "%s %s (%s / %s)", ic, e.Name(), fmtSize(e.SizeModified()), fmtSize(e.Size()), ) } if e.isExec { return ColorGreen.Sprintf("%s %s (%s)", ic, e.Name(), fmtSize(e.Size())) } return fmt.Sprintf("%s %s (%s)", ic, e.Name(), fmtSize(e.Size())) } func (e *FileEntry) stringRecursive(depth, maxDepth int, b *strings.Builder) { if depth > maxDepth { return } b.WriteString(strings.Repeat(" ", depth)) b.WriteString(e.String()) b.WriteRune('\n') } func (e *FileEntry) StringRecursive(depth int) string { b := new(strings.Builder) e.stringRecursive(0, depth, b) return b.String() } func (e *FileEntry) Scan(ch chan<- string, mounts map[string]struct{}) { if ch != nil { ch <- e.Path() } s := uint64(0) if fi, err := os.Lstat(e.Path()); err == nil { s = uint64(fi.Size()) e.isExec = fi.Mode()&0x41 > 0 // 0x41 == 0b001000001 == rwXrwxrwX } else if errors.Is(err, os.ErrPermission) { e.noPerm = true } e.size = &s e.modSize = new(uint64) *e.modSize = *e.size f := uint64(1) e.fileCount = &f e.modFileCount = new(uint64) *e.modFileCount = *e.fileCount } func (e *FileEntry) Parent() Entry { return e.parent } func (e *FileEntry) Entry(path string) Entry { return e } func (e *FileEntry) IsDir() bool { return false } func (e *FileEntry) SetRemovalMark(mark bool) { e.removal = mark if mark { e.modify(-*e.size, -*e.fileCount) // underflow that is cancelling out perfectly } else { e.modify(*e.size, *e.fileCount) } // recalculate order of parent entries pentries := e.Parent().Entries() sort.Slice(pentries, func(i, j int) bool { return pentries[i].SizeModified() < pentries[j].SizeModified() }) } func (e *FileEntry) RemovalMark() bool { return e.removal } func (e *FileEntry) SizeModified() uint64 { if e.size == nil { e.Size() } return *e.modSize } func (e *FileEntry) modify(size uint64, fileCount uint64) { *e.modSize = *e.modSize + size *e.modFileCount = *e.modFileCount + fileCount if e.parent != nil { e.parent.modify(size, fileCount) } } func (e *FileEntry) RemovalStats(stats *RemovalStats) { if e.RemovalMark() { stats.Files++ stats.Bytes += *e.size if e.Parent() != nil && !e.Parent().RemovalMark() { stats.Paths = append(stats.Paths, e.Path()) } } } func (e *FileEntry) FileCount() uint64 { return *e.fileCount } func (e *FileEntry) FileCountModified() uint64 { return *e.modFileCount }