201 lines
4.0 KiB
Go
201 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.milar.in/milarin/buildinfo"
|
|
"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)")
|
|
ShowVersion = flag.Bool("v", false, "show version and exit")
|
|
RecursiveDirDepth = flag.Int("d", 1, "depth for recursively printing files of directories")
|
|
)
|
|
|
|
// 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?)
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if *ShowVersion {
|
|
buildinfo.Print(buildinfo.Options{})
|
|
return
|
|
}
|
|
|
|
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(*RecursiveDirDepth))
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|