diskspace/main.go

201 lines
3.9 KiB
Go
Raw Permalink Normal View History

2022-06-28 13:11:16 +02:00
package main
import (
"bufio"
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
2022-08-29 13:48:46 +02:00
"git.milar.in/milarin/buildinfo"
2022-06-28 13:11:16 +02:00
"github.com/fatih/color"
)
var (
Current Entry
Root Entry
ColorRed = color.New(color.FgRed)
ColorBlue = color.New(color.FgBlue)
ColorGreen = color.New(color.FgGreen)
IconTheme = flag.String("i", "unicode", "icon theme (currently supported: default, unicode, nerd)")
2022-08-29 13:48:46 +02:00
ShowVersion = flag.Bool("v", false, "show version and exit")
2022-06-28 13:11:16 +02:00
)
2022-08-03 21:28:45 +02:00
// FIXME sometimes sorting not applied after removing items
// TODO pwd command showing Current entry path and Root entry path in filesystem
// TODO arrow up -> last command (`stty -raw` necessary?). even better: rlwrap impl (see tsoding noq)
// TODO config file with preferred icon theme or read best icon theme from terminfo somehow
// TODO on exit: show deleted files in recursive tree (only deleted files and their parent directories)
// TODO autocomplete on TAB (see arrow up issue: raw necessary?)
2022-06-28 13:11:16 +02:00
func main() {
flag.Parse()
2022-08-29 13:48:46 +02:00
if *ShowVersion {
buildinfo.Print(buildinfo.Options{})
return
}
2022-06-28 13:11:16 +02:00
rootPath := flag.Arg(0)
mounts, err := Mounts()
if err != nil {
fmt.Fprintln(os.Stderr, "Mount points could not be read! All mount points will be calculated as well")
}
if abs, err := filepath.Abs(rootPath); err == nil {
rootPath = abs
} else {
panic(err)
}
root, err := NewEntry(rootPath)
if err != nil {
panic(err)
}
start := time.Now()
ScanEntryLive(root, mounts)
fmt.Println(Translate("Scanning took %s", time.Since(start)))
fmt.Println()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
for range c {
fmt.Println()
showError("Use command 'exit' for exiting properly")
fmt.Print("> ")
}
}()
Root = root
Current = root
s := bufio.NewScanner(os.Stdin)
showCurrentDir := true
for {
if showCurrentDir {
fmt.Println(Current.StringRecursive(1))
}
fmt.Print("> ")
s.Scan()
input := s.Text()
showCurrentDir = handleInput(input)
}
}
func handleInput(input string) bool {
cmd := strings.Split(strings.TrimSpace(input), " ")
switch cmd[0] {
case "ls":
ls(cmd[1:])
return false
case "cd":
cd(cmd[1:])
case "rm":
rm(cmd[1:], true)
case "urm":
rm(cmd[1:], false)
case "exit":
exit()
case "help":
help(cmd[1:])
}
return true
}
func showError(str string, args ...interface{}) {
fmt.Println()
ColorRed.Println(Translate(str, args...))
fmt.Println()
}
func entryByPath(path string) Entry {
if !strings.HasPrefix(path, Root.Path()) {
return nil
}
path = strings.TrimPrefix(path, Root.Path())
if path == "/" || path == "" {
return Root
} else {
path = strings.TrimPrefix(path, "/")
}
return Root.Entry(path)
}
func getEntriesForPath(path string) []Entry {
path = filepath.Clean(path)
if filepath.IsAbs(path) {
path = filepath.Join(Root.Path(), path)
} else {
path = filepath.Join(Current.Path(), path)
}
paths, err := filepath.Glob(path)
if err != nil {
panic(err)
}
entries := make([]Entry, 0, len(paths))
for _, path := range paths {
if entry := entryByPath(path); entry != nil {
entries = append(entries, entry)
}
}
return entries
}
func ScanEntryLive(entry Entry, mounts map[string]struct{}) {
ch := make(chan string, 10)
wg := new(sync.WaitGroup)
// TODO cap string size to terminal width
//width, _, _ := terminal.GetSize(int(os.Stdin.Fd()))
wg.Add(1)
go func(ch <-chan string) {
defer wg.Done()
for path := range ch {
os.Stdout.Write([]byte{'\r'})
clearEOL()
fmt.Print(Translate("Scanning ") + path)
}
os.Stdout.Write([]byte{'\r'})
clearEOL()
}(ch)
entry.Scan(ch, mounts)
close(ch)
wg.Wait()
}
func removeMarkedEntries(entry Entry) {
if entry.RemovalMark() {
err := os.RemoveAll(entry.Path())
if err != nil {
ColorRed.Println(Translate("Could not delete '%s': %s", entry.Path(), err.Error()))
}
} else {
for _, entry := range entry.Entries() {
removeMarkedEntries(entry)
}
}
}