ruspa/tree/dynamic_tree.go

553 lines
11 KiB
Go
Raw Permalink Normal View History

package tree
2020-03-01 23:09:00 +01:00
import (
"errors"
"fmt"
"path/filepath"
"strings"
)
const (
TopType = "top"
IntermediateType = "intermediate"
BottomType = "bottom"
SingleType = "single"
)
var ErrUnknownType = errors.New("unknown type")
var ErrShouldBeBottom = errors.New("should be of type Bottom")
var ErrChannelError = errors.New("channel errored")
2020-03-03 14:22:12 +01:00
var ErrUnknownUnit = errors.New("unknown unit")
2020-03-01 23:09:00 +01:00
var (
2020-03-19 00:11:11 +01:00
NodeB = "│"
NodeT = "├──"
NodeL = "└──"
NodePad = " "
CloseLine = "\033[K\n"
2020-03-01 23:09:00 +01:00
)
type Node interface {
2020-03-03 14:22:12 +01:00
SetUnit(string)
GetUnit() ByteSize
2020-03-01 23:09:00 +01:00
Size() int64
Spawn(int) error
Collect() error
2020-03-07 17:23:06 +01:00
AddCollector(chan int64)
2020-03-19 00:11:11 +01:00
SetLimits(int64, int64)
2020-03-01 23:09:00 +01:00
Level() int
2020-03-07 17:23:06 +01:00
Name() string
Complete() bool
2020-03-08 20:58:13 +01:00
Depth() int
2020-03-01 23:09:00 +01:00
}
//////////////////////////////
/////////// Top //////////////
//////////////////////////////
type Top struct {
2020-03-07 17:23:06 +01:00
path string
size int64
unit ByteSize
tree []Node
collect []<-chan int64
2020-03-19 00:11:11 +01:00
minLimit int64
maxLimit int64
2020-03-07 17:23:06 +01:00
level int
complete bool
2020-03-01 23:09:00 +01:00
}
func NewTop(path string) *Top {
2020-03-03 14:22:12 +01:00
t := &Top{path: path, level: 0, unit: KB}
2020-03-01 23:09:00 +01:00
return t
}
2020-03-03 14:22:12 +01:00
func (t *Top) SetUnit(unit string) {
2020-03-19 00:11:11 +01:00
t.unit = parseUnit(unit)
2020-03-03 14:22:12 +01:00
}
func (t *Top) GetUnit() ByteSize {
return t.unit
}
2020-03-01 23:09:00 +01:00
func (t *Top) Size() int64 {
return t.size
}
func (t *Top) Spawn(maxLevel int) error {
if t.Level() >= maxLevel {
return ErrShouldBeBottom
}
files, err := ListDir(t.path)
if err != nil {
return err
}
for _, info := range files {
switch mode := info.Mode(); {
case mode.IsDir():
var child Node
2020-03-07 17:23:06 +01:00
var collect chan int64
2020-03-01 23:09:00 +01:00
path := filepath.Join(t.path, info.Name())
if isLastLevel(t, maxLevel) || !AnyDirectoryDownThere(path) {
2020-03-07 17:23:06 +01:00
child, collect = t.newBottom(path)
2020-03-01 23:09:00 +01:00
} else {
2020-03-07 17:23:06 +01:00
child, collect = t.newIntermediate(path)
2020-03-01 23:09:00 +01:00
}
2020-03-07 17:23:06 +01:00
t.AddCollector(collect)
2020-03-01 23:09:00 +01:00
go child.Spawn(maxLevel)
case !mode.IsDir() && mode.IsRegular():
2020-03-07 17:23:06 +01:00
t.size += info.Size()
2020-03-01 23:09:00 +01:00
}
}
2020-03-07 17:23:06 +01:00
go t.Collect()
2020-03-01 23:09:00 +01:00
return nil
}
func (t *Top) Collect() error {
2020-03-07 17:23:06 +01:00
for s := range merge(t.collect) {
2020-03-01 23:09:00 +01:00
t.size += s
}
2020-03-07 17:23:06 +01:00
t.complete = true
2020-03-01 23:09:00 +01:00
return nil
}
2020-03-07 17:23:06 +01:00
func (t *Top) AddCollector(collect chan int64) {
t.collect = append(t.collect, collect)
2020-03-01 23:09:00 +01:00
}
2020-03-19 00:11:11 +01:00
func (t *Top) SetLimits(min, max int64) {
t.minLimit = min
t.maxLimit = max
}
2020-03-01 23:09:00 +01:00
func (t *Top) Level() int {
return t.level
}
func (t *Top) Name() string {
return filepath.Base(t.path)
}
2020-03-07 17:23:06 +01:00
func (t *Top) Complete() bool {
for _, child := range t.tree {
if !child.Complete() {
return false
}
}
return true
}
2020-03-08 20:58:13 +01:00
func (t *Top) Depth() int {
var depth int
for _, child := range t.tree {
2020-03-19 00:11:11 +01:00
if isIntoLimits(child.Size(), t.minLimit, t.maxLimit) {
depth += child.Depth()
}
2020-03-08 20:58:13 +01:00
}
return depth
}
2020-03-01 23:09:00 +01:00
func (t *Top) String() string {
var out string
var lines []string
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf("(%s) %s%s", fmtSize(t.Size(), t.GetUnit()), t.path, CloseLine)
treeSize := 0
2020-03-01 23:09:00 +01:00
for _, child := range t.tree {
2020-03-19 00:11:11 +01:00
if isIntoLimits(child.Size(), t.minLimit, t.maxLimit) {
lines = append(lines, fmt.Sprintf("%s", child))
treeSize += 1
}
2020-03-01 23:09:00 +01:00
}
for a := 0; a < treeSize-1; a++ {
childLines := strings.Split(lines[a], "\n")
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf(" %s%s%s", NodeT, childLines[0], CloseLine)
2020-03-01 23:09:00 +01:00
for b := 1; b < len(childLines); b++ {
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf(" %s%s%s", NodeB, childLines[b], CloseLine)
2020-03-01 23:09:00 +01:00
}
}
childLines := strings.Split(lines[treeSize-1], "\n")
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf(" %s%s%s", NodeL, childLines[0], CloseLine)
2020-03-01 23:09:00 +01:00
for a := 1; a < len(childLines); a++ {
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf("%s%s%s", " ", childLines[a], CloseLine)
2020-03-01 23:09:00 +01:00
}
return out
}
func (t *Top) addToTree(child Node) {
t.tree = append(t.tree, child)
}
2020-03-07 17:23:06 +01:00
func (t *Top) newChild(path, kind string) (Node, chan int64) {
2020-03-01 23:09:00 +01:00
switch {
case kind == IntermediateType:
2020-03-07 17:23:06 +01:00
i, collect := NewIntermediate(path, t)
2020-03-01 23:09:00 +01:00
t.addToTree(i)
2020-03-07 17:23:06 +01:00
return i, collect
2020-03-01 23:09:00 +01:00
case kind == BottomType:
2020-03-07 17:23:06 +01:00
b, collect := NewBottom(path, t)
2020-03-01 23:09:00 +01:00
t.addToTree(b)
2020-03-07 17:23:06 +01:00
return b, collect
2020-03-01 23:09:00 +01:00
default:
panic(ErrUnknownType)
}
}
2020-03-07 17:23:06 +01:00
func (t *Top) newIntermediate(path string) (*Intermediate, chan int64) {
child, collect := t.newChild(path, IntermediateType)
return child.(*Intermediate), collect
2020-03-01 23:09:00 +01:00
}
2020-03-07 17:23:06 +01:00
func (t *Top) newBottom(path string) (*Bottom, chan int64) {
child, collect := t.newChild(path, BottomType)
return child.(*Bottom), collect
2020-03-01 23:09:00 +01:00
}
//////////////////////////////
/////// Intermediate /////////
//////////////////////////////
type Intermediate struct {
2020-03-07 17:23:06 +01:00
path string
size int64
unit ByteSize
parent Node
tree []Node
collect []<-chan int64
refer chan int64
2020-03-19 00:11:11 +01:00
minLimit int64
maxLimit int64
2020-03-07 17:23:06 +01:00
level int
complete bool
}
func NewIntermediate(path string, parent Node) (*Intermediate, chan int64) {
refer := make(chan int64)
i := &Intermediate{path: path, refer: refer, parent: parent}
i.collect = make([]<-chan int64, 1)
2020-03-01 23:09:00 +01:00
i.level = parent.Level() + 1
2020-03-03 14:22:12 +01:00
i.unit = parent.GetUnit()
2020-03-07 17:23:06 +01:00
return i, refer
2020-03-01 23:09:00 +01:00
}
2020-03-03 14:22:12 +01:00
func (i *Intermediate) SetUnit(unit string) {
2020-03-19 00:11:11 +01:00
i.unit = parseUnit(unit)
2020-03-03 14:22:12 +01:00
}
func (i *Intermediate) GetUnit() ByteSize {
return i.unit
}
2020-03-01 23:09:00 +01:00
func (i *Intermediate) Size() int64 {
return i.size
}
func (i *Intermediate) Spawn(maxLevel int) error {
if i.Level() >= maxLevel {
return ErrShouldBeBottom
}
files, err := ListDir(i.path)
if err != nil {
return err
}
for _, info := range files {
switch mode := info.Mode(); {
case mode.IsDir():
var child Node
2020-03-07 17:23:06 +01:00
var collect chan int64
2020-03-01 23:09:00 +01:00
path := filepath.Join(i.path, info.Name())
if isLastLevel(i, maxLevel) || !AnyDirectoryDownThere(path) {
2020-03-07 17:23:06 +01:00
child, collect = i.newBottom(path)
2020-03-01 23:09:00 +01:00
} else {
2020-03-07 17:23:06 +01:00
child, collect = i.newIntermediate(path)
2020-03-01 23:09:00 +01:00
}
2020-03-07 17:23:06 +01:00
i.AddCollector(collect)
2020-03-01 23:09:00 +01:00
go child.Spawn(maxLevel)
case !mode.IsDir() && mode.IsRegular():
2020-03-07 17:23:06 +01:00
i.size += info.Size()
i.refer <- info.Size()
2020-03-01 23:09:00 +01:00
}
}
2020-03-07 17:23:06 +01:00
go i.Collect()
2020-03-01 23:09:00 +01:00
return nil
}
func (i *Intermediate) Collect() error {
2020-03-07 17:23:06 +01:00
for s := range merge(i.collect) {
2020-03-01 23:09:00 +01:00
i.size += s
2020-03-03 14:16:54 +01:00
i.refer <- s
2020-03-01 23:09:00 +01:00
}
2020-03-07 17:23:06 +01:00
i.complete = true
2020-03-01 23:09:00 +01:00
return nil
}
2020-03-07 17:23:06 +01:00
func (i *Intermediate) AddCollector(collect chan int64) {
i.collect = append(i.collect, collect)
2020-03-01 23:09:00 +01:00
}
2020-03-19 00:11:11 +01:00
func (i *Intermediate) SetLimits(min, max int64) {
i.minLimit = min
i.maxLimit = max
}
2020-03-01 23:09:00 +01:00
func (i *Intermediate) Level() int {
return i.level
}
func (i *Intermediate) Name() string {
return filepath.Base(i.path)
}
2020-03-07 17:23:06 +01:00
func (i *Intermediate) Complete() bool {
for _, child := range i.tree {
if !child.Complete() {
return false
}
}
return true
}
2020-03-08 20:58:13 +01:00
func (i *Intermediate) Depth() int {
var depth int
for _, child := range i.tree {
2020-03-19 00:11:11 +01:00
if isIntoLimits(child.Size(), i.minLimit, i.maxLimit) {
depth += child.Depth()
}
2020-03-08 20:58:13 +01:00
}
return depth
}
2020-03-01 23:09:00 +01:00
func (i *Intermediate) String() string {
var lines []string
2020-03-03 14:22:12 +01:00
out := fmt.Sprintf("(%s) %s\n", fmtSize(i.Size(), i.GetUnit()), i.Name())
2020-03-19 00:11:11 +01:00
treeSize := 0
2020-03-01 23:09:00 +01:00
for _, child := range i.tree {
2020-03-19 00:11:11 +01:00
if isIntoLimits(child.Size(), i.minLimit, i.maxLimit) {
lines = append(lines, fmt.Sprintf("%s", child))
treeSize += 1
}
2020-03-01 23:09:00 +01:00
}
for a := 0; a < treeSize-1; a++ {
childLines := strings.Split(lines[a], "\n")
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf("%s%s%s", NodePad, NodeT, childLines[0])
2020-03-01 23:09:00 +01:00
for b := 1; b < len(childLines); b++ {
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf("%s%s%s", NodePad, NodeB, childLines[b])
2020-03-01 23:09:00 +01:00
}
}
childLines := strings.Split(lines[treeSize-1], "\n")
lenChildLines := len(childLines)
if lenChildLines > 1 {
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf("%s%s%s", NodePad, NodeL, childLines[0])
for a := 1; a < lenChildLines-2; a++ {
2020-03-19 00:11:11 +01:00
out += fmt.Sprintf(" %s%s", NodePad, childLines[a])
}
2020-03-03 14:22:12 +01:00
out += fmt.Sprintf(" %s%s", NodePad, childLines[lenChildLines-1])
} else {
out += fmt.Sprintf("%s%s%s", NodePad, NodeL, childLines[0])
2020-03-01 23:09:00 +01:00
}
return out
}
func (i *Intermediate) addToTree(child Node) {
i.tree = append(i.tree, child)
}
2020-03-07 17:23:06 +01:00
func (i *Intermediate) newChild(path, kind string) (Node, chan int64) {
2020-03-01 23:09:00 +01:00
switch {
case kind == IntermediateType:
2020-03-07 17:23:06 +01:00
c, collect := NewIntermediate(path, i)
2020-03-01 23:09:00 +01:00
i.addToTree(c)
2020-03-07 17:23:06 +01:00
return c, collect
2020-03-01 23:09:00 +01:00
case kind == BottomType:
2020-03-07 17:23:06 +01:00
b, collect := NewBottom(path, i)
2020-03-01 23:09:00 +01:00
i.addToTree(b)
2020-03-07 17:23:06 +01:00
return b, collect
2020-03-01 23:09:00 +01:00
default:
panic(ErrUnknownType)
}
}
2020-03-07 17:23:06 +01:00
func (i *Intermediate) newIntermediate(path string) (*Intermediate, chan int64) {
child, collect := i.newChild(path, IntermediateType)
return child.(*Intermediate), collect
2020-03-01 23:09:00 +01:00
}
2020-03-07 17:23:06 +01:00
func (i *Intermediate) newBottom(path string) (*Bottom, chan int64) {
child, collect := i.newChild(path, BottomType)
return child.(*Bottom), collect
2020-03-01 23:09:00 +01:00
}
//////////////////////////////
////////// Bottom ////////////
//////////////////////////////
type Bottom struct {
2020-03-07 17:23:06 +01:00
path string
size int64
unit ByteSize
parent Node
walker Walker
collect chan int64
refer chan int64
2020-03-19 00:11:11 +01:00
minLimit int64
maxLimit int64
2020-03-07 17:23:06 +01:00
level int
complete bool
}
func NewBottom(path string, parent Node) (*Bottom, chan int64) {
refer := make(chan int64)
b := &Bottom{path: path, refer: refer, parent: parent, unit: parent.GetUnit()}
2020-03-01 23:09:00 +01:00
b.level = parent.Level() + 1
2020-03-03 14:22:12 +01:00
b.unit = parent.GetUnit()
2020-03-07 17:23:06 +01:00
return b, refer
2020-03-01 23:09:00 +01:00
}
2020-03-03 14:22:12 +01:00
func (b *Bottom) SetUnit(unit string) {
2020-03-19 00:11:11 +01:00
b.unit = parseUnit(unit)
2020-03-03 14:22:12 +01:00
}
func (b *Bottom) GetUnit() ByteSize {
return b.unit
}
2020-03-01 23:09:00 +01:00
func (b *Bottom) Size() int64 {
return b.size
}
func (b *Bottom) Spawn(maxLevel int) error {
2020-03-07 17:23:06 +01:00
collect := make(chan int64)
b.AddCollector(collect)
b.walker = NewRealWalker(b.path, collect)
2020-03-01 23:09:00 +01:00
go b.walker.Walk()
2020-03-07 17:23:06 +01:00
go b.Collect()
2020-03-01 23:09:00 +01:00
return nil
}
func (b *Bottom) Collect() error {
for s := range b.collect {
b.size += s
2020-03-03 14:16:54 +01:00
b.refer <- s
2020-03-01 23:09:00 +01:00
}
2020-03-07 17:23:06 +01:00
b.complete = true
2020-03-01 23:09:00 +01:00
return nil
}
2020-03-07 17:23:06 +01:00
func (b *Bottom) AddCollector(collect chan int64) {
b.collect = collect
2020-03-01 23:09:00 +01:00
}
2020-03-19 00:11:11 +01:00
func (b *Bottom) SetLimits(min, max int64) {
b.minLimit = min
b.maxLimit = max
}
2020-03-01 23:09:00 +01:00
func (b *Bottom) Level() int {
return b.level
}
func (b *Bottom) Name() string {
return filepath.Base(b.path)
}
2020-03-07 17:23:06 +01:00
func (b *Bottom) Complete() bool {
return b.complete
}
2020-03-08 20:58:13 +01:00
func (b *Bottom) Depth() int {
return 1
}
2020-03-01 23:09:00 +01:00
func (b *Bottom) String() string {
2020-03-03 14:22:12 +01:00
return fmt.Sprintf("(%s) %s", fmtSize(b.Size(), b.GetUnit()), b.Name())
2020-03-01 23:09:00 +01:00
}
//////////////////////////////
////////// Single ////////////
//////////////////////////////
type Single struct {
2020-03-07 17:23:06 +01:00
path string
size int64
unit ByteSize
walker Walker
collect chan int64
complete bool
}
func NewSingle(path string) *Single {
s := &Single{path: path}
collect := make(chan int64)
s.AddCollector(collect)
return s
}
func (s *Single) SetUnit(unit string) {
2020-03-19 00:11:11 +01:00
s.unit = parseUnit(unit)
2020-03-07 17:23:06 +01:00
}
func (s *Single) GetUnit() ByteSize {
return s.unit
2020-03-01 23:09:00 +01:00
}
func (s *Single) Size() int64 {
return s.size
}
func (s *Single) Spawn(maxLevel int) error {
s.walker = NewRealWalker(s.path, s.collect)
go s.walker.Walk()
2020-03-07 17:23:06 +01:00
go s.Collect()
2020-03-01 23:09:00 +01:00
return nil
}
func (s *Single) Collect() error {
for v := range s.collect {
s.size += v
}
2020-03-07 17:23:06 +01:00
s.complete = true
2020-03-01 23:09:00 +01:00
return nil
}
2020-03-07 17:23:06 +01:00
func (s *Single) AddCollector(collect chan int64) {
s.collect = collect
2020-03-01 23:09:00 +01:00
}
2020-03-19 00:11:11 +01:00
func (s *Single) SetLimits(min, max int64) {
// just ignore limits
return
}
2020-03-01 23:09:00 +01:00
func (s *Single) Level() int {
return 0
}
2020-03-07 17:23:06 +01:00
func (s *Single) Name() string {
return filepath.Base(s.path)
}
func (s *Single) Complete() bool {
return s.complete
}
2020-03-08 20:58:13 +01:00
func (s *Single) Depth() int {
return 1
}
2020-03-07 17:23:06 +01:00
func (s *Single) String() string {
return fmt.Sprintf("(%s) %s\n", fmtSize(s.Size(), s.GetUnit()), s.Name())
2020-03-01 23:09:00 +01:00
}
//////////////////////////////
////////// Walker ////////////
//////////////////////////////
type Walker interface {
Walk()
}