This commit is contained in:
Blallo 2020-03-01 23:09:00 +01:00
commit 0bd99eaff7
No known key found for this signature in database
GPG key ID: 0CBE577C9B72DC3F
6 changed files with 663 additions and 0 deletions

29
dir_utils.go Normal file
View file

@ -0,0 +1,29 @@
package main
import (
"os"
)
func ListDir(path string) ([]os.FileInfo, error) {
var files []os.FileInfo
f, err := os.Open(path)
if err != nil {
return files, err
}
defer f.Close()
return f.Readdir(-1)
}
func AnyDirectoryDownThere(path string) bool {
files, err := ListDir(path)
if err != nil {
panic(err)
}
for _, file := range files {
if file.IsDir() {
return true
}
}
return false
}

95
dir_utils_test.go Normal file
View file

@ -0,0 +1,95 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
var subdirs = map[string][]string{"sub1": []string{"s1s1", "s1s2", "s1s3"}, "sub2": []string{"s2s1", "s2s2"}, "sub3": []string{}}
var dirWithSubdirs string
var dirWithoutSubdirs string
func mkchild(root, dir string) (string, error) {
childPath := filepath.Join(root, dir)
var mode os.FileMode = 2_148_532_735 // means "dtrwxrwxrwx"
err := os.Mkdir(childPath, mode)
return childPath, err
}
func createTestDirs() string {
root, err := ioutil.TempDir("", "ruspa_*")
if err != nil {
panic(err)
}
for dirName, subdirList := range subdirs {
dir, err := mkchild(root, dirName)
if err != nil {
panic(err)
}
for _, subdir := range subdirList {
if _, err = mkchild(dir, subdir); err != nil {
panic(err)
}
}
}
return root
}
func removeTestDirs(root string) {
for dirName, subdirList := range subdirs {
dirPath := filepath.Join(root, dirName)
for _, subdir := range subdirList {
subdirPath := filepath.Join(dirPath, subdir)
if err := os.Remove(subdirPath); err != nil {
panic(err)
}
}
err := os.Remove(dirPath)
if err != nil {
panic(err)
}
}
if err := os.Remove(root); err != nil {
panic(err)
}
}
func TestListDir(t *testing.T) {
root := createTestDirs()
defer removeTestDirs(root)
dirs, err := ListDir(root)
if err != nil {
t.Error(err)
}
for _, dir := range dirs {
if _, ok := subdirs[dir.Name()]; !ok {
t.Errorf("missing %s in %s\n", dir, dirs)
}
}
shouldBeEmpty, err := ListDir(filepath.Join(root, "sub3"))
if err != nil {
panic(err)
}
if len(shouldBeEmpty) != 0 {
t.Error("ListDir of sub3 should be empty")
}
}
func TestAnyDirectoryDownThere(t *testing.T) {
root := createTestDirs()
defer removeTestDirs(root)
pathNotEmpty := filepath.Join(root, "sub1")
if !AnyDirectoryDownThere(pathNotEmpty) {
list, _ := ListDir(pathNotEmpty)
t.Logf("%s -> %s\n", pathNotEmpty, list)
t.Error("sub1 is empty, but should be")
}
pathEmpty := filepath.Join(root, "sub3")
if AnyDirectoryDownThere(pathEmpty) {
list, _ := ListDir(pathEmpty)
t.Logf("%s -> %s\n", pathEmpty, list)
t.Error("sub3 is NOT empty, but it should be")
}
}

401
dynamic_tree.go Normal file
View file

@ -0,0 +1,401 @@
package main
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")
var (
NodeB = "│"
NodeT = "├──"
NodeL = "└──"
NodePad = " "
)
type Node interface {
Size() int64
Spawn(int) error
Collect() error
Collector() chan int64
Level() int
}
func isLastLevel(node Node, maxLevel int) bool {
return maxLevel-1 == node.Level()
}
func isBeforeLastLevel(node Node, maxLevel int) bool {
return maxLevel-1 > node.Level()
}
//////////////////////////////
/////////// Top //////////////
//////////////////////////////
type Top struct {
path string
size int64
tree []Node
collect chan int64
level int
}
func NewTop(path string) *Top {
t := &Top{path: path, level: 0}
t.collect = make(chan int64)
return t
}
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
path := filepath.Join(t.path, info.Name())
if isLastLevel(t, maxLevel) || !AnyDirectoryDownThere(path) {
child = t.newBottom(path)
} else {
child = t.newIntermediate(path)
}
go child.Spawn(maxLevel)
defer func() {
go child.Collect()
}()
case !mode.IsDir() && mode.IsRegular():
t.collect <- info.Size()
}
}
return nil
}
func (t *Top) Collect() error {
for s := range t.collect {
t.size += s
}
return nil
}
func (t *Top) Collector() chan int64 {
return t.collect
}
func (t *Top) Level() int {
return t.level
}
func (t *Top) Name() string {
return filepath.Base(t.path)
}
func (t *Top) String() string {
var out string
var lines []string
out += fmt.Sprintf("(%d) %s\n", t.Size(), t.path)
treeSize := len(t.tree)
for _, child := range t.tree {
lines = append(lines, fmt.Sprintf("%s", child))
}
for a := 0; a < treeSize-1; a++ {
childLines := strings.Split(lines[a], "\n")
out += fmt.Sprintf(" %s%s\n", NodeT, childLines[0])
for b := 1; b < len(childLines); b++ {
out += fmt.Sprintf(" %s%s\n", NodeB, childLines[b])
}
}
childLines := strings.Split(lines[treeSize-1], "\n")
out += fmt.Sprintf(" %s%s\n", NodeL, childLines[0])
for a := 1; a < len(childLines); a++ {
out += fmt.Sprintf(" %s%s\n", NodePad, childLines[a])
}
return out
}
func (t *Top) addToTree(child Node) {
t.tree = append(t.tree, child)
}
func (t *Top) newChild(path, kind string) Node {
switch {
case kind == IntermediateType:
i := NewIntermediate(path, t)
t.addToTree(i)
return i
case kind == BottomType:
b := NewBottom(path, t)
t.addToTree(b)
return b
default:
panic(ErrUnknownType)
}
}
func (t *Top) newIntermediate(path string) *Intermediate {
return t.newChild(path, IntermediateType).(*Intermediate)
}
func (t *Top) newBottom(path string) *Bottom {
return t.newChild(path, BottomType).(*Bottom)
}
//////////////////////////////
/////// Intermediate /////////
//////////////////////////////
type Intermediate struct {
path string
size int64
parent Node
tree []Node
collect chan int64
refer chan int64
level int
}
func NewIntermediate(path string, parent Node) *Intermediate {
i := &Intermediate{path: path, refer: parent.Collector(), parent: parent}
i.collect = make(chan int64)
i.level = parent.Level() + 1
return i
}
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
path := filepath.Join(i.path, info.Name())
if isLastLevel(i, maxLevel) || !AnyDirectoryDownThere(path) {
child = i.newBottom(path)
} else {
child = i.newIntermediate(path)
}
go child.Spawn(maxLevel)
defer func() {
go child.Collect()
}()
case !mode.IsDir() && mode.IsRegular():
i.collect <- info.Size()
}
}
return nil
}
func (i *Intermediate) Collect() error {
for s := range i.collect {
i.size += s
i.refer <- i.size
}
close(i.refer)
return nil
}
func (i *Intermediate) Collector() chan int64 {
return i.collect
}
func (i *Intermediate) Level() int {
return i.level
}
func (i *Intermediate) Name() string {
return filepath.Base(i.path)
}
func (i *Intermediate) String() string {
var lines []string
out := fmt.Sprintf("(%d) %s\n", i.Size(), i.Name())
treeSize := len(i.tree)
for _, child := range i.tree {
lines = append(lines, fmt.Sprintf("%s", child))
}
for a := 0; a < treeSize-1; a++ {
childLines := strings.Split(lines[a], "\n")
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeT, childLines[0])
for b := 1; b < len(childLines); b++ {
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeB, childLines[b])
}
}
childLines := strings.Split(lines[treeSize-1], "\n")
lenChildLines := len(childLines)
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeL, childLines[0])
for a := 1; a < lenChildLines-2; a++ {
out += fmt.Sprintf("%s%s%s\n", NodePad, NodePad, childLines[a])
}
if lenChildLines > 0 {
out += fmt.Sprintf("%s%s%s", NodePad, NodePad, childLines[lenChildLines-1])
}
return out
}
func (i *Intermediate) addToTree(child Node) {
i.tree = append(i.tree, child)
}
func (i *Intermediate) newChild(path, kind string) Node {
switch {
case kind == IntermediateType:
c := NewIntermediate(path, i)
i.addToTree(c)
return c
case kind == BottomType:
b := NewBottom(path, i)
i.addToTree(b)
return b
default:
panic(ErrUnknownType)
}
}
func (i *Intermediate) newIntermediate(path string) *Intermediate {
return i.newChild(path, IntermediateType).(*Intermediate)
}
func (i *Intermediate) newBottom(path string) *Bottom {
return i.newChild(path, BottomType).(*Bottom)
}
//////////////////////////////
////////// Bottom ////////////
//////////////////////////////
type Bottom struct {
path string
size int64
parent Node
walker Walker
collect chan int64
refer chan int64
level int
}
func NewBottom(path string, parent Node) *Bottom {
b := &Bottom{path: path, refer: parent.Collector(), parent: parent}
b.collect = make(chan int64)
b.level = parent.Level() + 1
return b
}
func (b *Bottom) Size() int64 {
return b.size
}
func (b *Bottom) Spawn(maxLevel int) error {
b.walker = NewRealWalker(b.path, b.collect)
go b.walker.Walk()
return nil
}
func (b *Bottom) Collect() error {
for s := range b.collect {
b.size += s
b.refer <- b.size
}
close(b.refer)
return nil
}
func (b *Bottom) Collector() chan int64 {
return b.collect
}
func (b *Bottom) Level() int {
return b.level
}
func (b *Bottom) Name() string {
return filepath.Base(b.path)
}
func (b *Bottom) String() string {
return fmt.Sprintf("(%d) %s", b.Size(), b.Name())
}
//////////////////////////////
////////// Single ////////////
//////////////////////////////
type Single struct {
path string
size int64
walker Walker
collect chan int64
}
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()
return nil
}
func (s *Single) Collect() error {
for v := range s.collect {
s.size += v
}
return nil
}
func (s *Single) Collector() chan int64 {
return s.collect
}
func (s *Single) Level() int {
return 0
}
func NewSingle(path string) *Single {
s := &Single{path: path}
s.collect = make(chan int64)
return s
}
//////////////////////////////
////////// Walker ////////////
//////////////////////////////
type Walker interface {
Walk()
}

84
dynamic_tree_test.go Normal file
View file

@ -0,0 +1,84 @@
package main
import (
"fmt"
"testing"
)
const result = `(0) /
(0) i1
(0) i1s1
(0) i1s1b1
(0) i1s1b2
(0) i1s1b3
(0) i1s1b3
(0) i1s2
(0) i1s2b1
(0) i1s2b2
(0) i1s2b2
(0) i1s3
(0) i1s3s1
(0) i1s3s1b1
(0) i1s3s1b2
(0) i1s3s1b3
(0) i1s3s1b3
(0) i1s3b1
(0) i1s3b1
(0) i1s4
(0) i1s4b1
(0) i2
(0) i2b1
(0) i2b2
(0) i2b2
(0) i3
(0) i3s1
(0) i3s1b1
(0) i3s1b1
(0) i3s2
(0) i3s2b1
(0) i3s2b2
(0) i3s2b2
(0) i3s3
(0) i3s3b1
`
func createTree() *Top {
t := NewTop("/")
i1 := t.newChild("i1", IntermediateType).(*Intermediate)
i2 := t.newChild("i2", IntermediateType).(*Intermediate)
i3 := t.newChild("i3", IntermediateType).(*Intermediate)
i1s1 := i1.newChild("i1s1", IntermediateType).(*Intermediate)
i1s2 := i1.newChild("i1s2", IntermediateType).(*Intermediate)
i1s3 := i1.newChild("i1s3", IntermediateType).(*Intermediate)
i1s4 := i1.newChild("i1s4", IntermediateType).(*Intermediate)
i3s1 := i3.newChild("i3s1", IntermediateType).(*Intermediate)
i3s2 := i3.newChild("i3s2", IntermediateType).(*Intermediate)
i3s3 := i3.newChild("i3s3", IntermediateType).(*Intermediate)
_ = i1s1.newChild("i1s1b1", BottomType).(*Bottom)
_ = i1s1.newChild("i1s1b2", BottomType).(*Bottom)
_ = i1s1.newChild("i1s1b3", BottomType).(*Bottom)
_ = i1s2.newChild("i1s2b1", BottomType).(*Bottom)
_ = i1s2.newChild("i1s2b2", BottomType).(*Bottom)
i1s3s1 := i1s3.newChild("i1s3s1", IntermediateType).(*Intermediate)
_ = i1s3.newChild("i1s3b1", BottomType).(*Bottom)
_ = i1s4.newChild("i1s4b1", BottomType).(*Bottom)
_ = i2.newChild("i2b1", BottomType).(*Bottom)
_ = i2.newChild("i2b2", BottomType).(*Bottom)
_ = i3s1.newChild("i3s1b1", BottomType).(*Bottom)
_ = i3s2.newChild("i3s2b1", BottomType).(*Bottom)
_ = i3s2.newChild("i3s2b2", BottomType).(*Bottom)
_ = i3s3.newChild("i3s3b1", BottomType).(*Bottom)
_ = i1s3s1.newChild("i1s3s1b1", BottomType).(*Bottom)
_ = i1s3s1.newChild("i1s3s1b2", BottomType).(*Bottom)
_ = i1s3s1.newChild("i1s3s1b3", BottomType).(*Bottom)
return t
}
func TestString(t *testing.T) {
tree := createTree()
repr := fmt.Sprintf("%s", tree)
if repr != result {
t.Errorf("%s", repr)
}
}

26
main.go Normal file
View file

@ -0,0 +1,26 @@
package main
import (
"flag"
"fmt"
"time"
)
func main() {
var path string
var depth int
flag.StringVar(&path, "path", ".", "Path from where to start the walk from")
flag.IntVar(&depth, "depth", 0, "Depth to display")
flag.Parse()
t := NewTop(path)
go t.Spawn(depth)
go t.Collect()
for {
select {
case <-time.After(500 * time.Millisecond):
fmt.Printf("\033[H\033[2J")
fmt.Println(t)
}
}
}

28
real_walker.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"os"
"path/filepath"
)
type RealWalker struct {
path string
report chan int64
}
func (r *RealWalker) walkFunc(path string, info os.FileInfo, err error) error {
switch mode := info.Mode(); {
case mode.IsRegular():
r.report <- info.Size()
}
return nil
}
func (r *RealWalker) Walk() {
filepath.Walk(r.path, r.walkFunc)
}
func NewRealWalker(path string, report chan int64) *RealWalker {
r := &RealWalker{path: path, report: report}
return r
}