Blallo 5 years ago
commit
07ab12b271
4 changed files with 277 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 18 0
      main.go
  3. 61 0
      sizer/logger.go
  4. 197 0
      sizer/main.go

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/tests

+ 18 - 0
main.go

@@ -0,0 +1,18 @@
+package main
+
+import (
+	"flag"
+
+	"git.lattuga.net/blallo/ruspa/sizer"
+)
+
+func main() {
+	var path string
+	var debug bool
+	flag.StringVar(&path, "path", ".", "The base path.")
+	flag.BoolVar(&debug, "debug", false, "Toggle debug messages.")
+	flag.Parse()
+	sizer.Debug = debug
+	rootINode := sizer.NewINode(sizer.FileTypes["dir"], 0, path)
+	rootINode.Sizer()
+}

+ 61 - 0
sizer/logger.go

@@ -0,0 +1,61 @@
+package sizer
+
+import "log"
+
+// Debug is a global bool to toggle debugging messages.
+var Debug bool
+
+type console log.Logger
+
+func (l *console) SetFlags(i int) {
+	log.SetFlags(i)
+}
+
+func (l *console) Print(v ...interface{}) {
+	log.Print(v...)
+}
+
+func (l *console) Printf(format string, v ...interface{}) {
+	log.Printf(format, v...)
+}
+
+func (l *console) Println(v ...interface{}) {
+	log.Println(v...)
+}
+
+func (l *console) Fatal(v ...interface{}) {
+	log.Fatal(v...)
+}
+
+func (l *console) Fatalf(format string, v ...interface{}) {
+	log.Fatalf(format, v...)
+}
+
+func (l *console) Fatalln(v ...interface{}) {
+	log.Fatalln(v...)
+}
+
+func (l *console) Debug(msg ...interface{}) {
+	if Debug {
+		l.Print(msg...)
+	}
+}
+
+func (l *console) Debugf(format string, msg ...interface{}) {
+	if Debug {
+		l.Printf(format, msg...)
+	}
+}
+
+func (l *console) Debugln(msg ...interface{}) {
+	if Debug {
+		l.Println(msg...)
+	}
+}
+
+// Console is the logger object
+var Console console
+
+func init() {
+	Console.SetFlags(0)
+}

+ 197 - 0
sizer/main.go

@@ -0,0 +1,197 @@
+package sizer
+
+import (
+	"errors"
+	"os"
+	"path/filepath"
+	"sync"
+)
+
+const (
+	// FileType is a const representing a file
+	FileType = iota
+	// DirType is a const representing a directory
+	DirType = iota
+	// SymLinkType is a const representing a symbolic link
+	SymLinkType = iota
+	// HardLinkType is a const representing a hard link
+	HardLinkType = iota
+	// CharDevType is a const representing a char device
+	CharDevType = iota
+	// NamedPipeType is a const representing a named pipe
+	NamedPipeType = iota
+	// SocketType is a const representing a socket, both unix and network
+	SocketType = iota
+)
+
+// FileTypes is a map containing the above defined types
+var FileTypes = map[string]int{
+	"file": FileType,
+	"dir":  DirType,
+	"slnk": SymLinkType,
+	"hlnk": HardLinkType,
+	"char": CharDevType,
+	"pipe": NamedPipeType,
+	"sock": SocketType,
+}
+
+// INode is an entry in the filesystem. It can be:
+//   - a file
+//   - a directory
+//   - a symbolic link
+//   - a hard link
+//   - a char device/named pipe/socket
+// It takes a Kind (as above described), a Size and a Path
+type INode struct {
+	Kind     int
+	Size     int64
+	Path     string
+	Children *INodeList
+}
+
+// INodeList is a simple *INode slice with a Mutex to allow safe
+// concurrent write.
+type INodeList struct {
+	Elements []*INode
+	safe     sync.Mutex
+}
+
+type statusReport struct {
+	path string
+	err  error
+}
+
+// Append appends safely an inode to the INodeList.
+func (il *INodeList) Append(node *INode) {
+	il.safe.Lock()
+	il.Elements = append(il.Elements, node)
+	il.safe.Unlock()
+}
+
+// INodeChan is a channel global to the sizer package that is used
+// to funnel the retrieved sizez and infos on the inodes.
+var INodeChan chan *INode
+
+// ErrNotIdenfiedType is the error returned when the a classifier does not
+// recognize the INode type as one present in FileTypes.
+var ErrNotIdenfiedType = errors.New("inode not identified")
+
+// NewINode returns a new *INode with kind, size and path
+func NewINode(kind int, size int64, path string) *INode {
+	var children INodeList
+	abspath, _ := filepath.Abs(path)
+	var newINode = INode{
+		Kind:     kind,
+		Size:     size,
+		Path:     abspath,
+		Children: &children,
+	}
+	return &newINode
+}
+
+// Name returns the name of the file at the INode.
+func (i *INode) Name() string {
+	_, name := filepath.Split(i.Path)
+	return name
+}
+
+// IdentifyType classifies a file-like object in one of the FileTypes.
+func IdentifyType(path string) (int, error) {
+	return -1, ErrNotIdenfiedType
+}
+
+func ls(path string) ([]string, error) {
+	var content []string
+	f, err := os.Open(path)
+	defer f.Close()
+	if err != nil {
+		return []string{}, err
+	}
+	files, err := f.Readdir(-1)
+	if err != nil {
+		return []string{}, err
+	}
+	for _, fi := range files {
+		content = append(content, filepath.Join(path, fi.Name()))
+	}
+	return content, nil
+}
+
+func runThrough(i *INode, ch chan int64, report chan statusReport, content []string) {
+	Console.Debugln("Entering:", i.Path)
+	for _, filepath := range content {
+		f, err := os.Lstat(filepath)
+		if err != nil {
+			report <- statusReport{path: i.Path, err: err}
+		}
+		switch mode := f.Mode(); {
+		case mode.IsDir():
+			Console.Debugln(filepath, "is a directory")
+			dirINode := NewINode(FileTypes["dir"], f.Size(), filepath)
+			i.Children.Append(dirINode)
+			ch <- f.Size()
+			go dirINode.walkDir(report)
+		case mode.IsRegular():
+			Console.Debugln(filepath, "is a regular file")
+			fileINode := NewINode(FileTypes["file"], f.Size(), filepath)
+			i.Children.Append(fileINode)
+			ch <- f.Size()
+		default:
+			Console.Debugln(filepath, "is NOT a regular file")
+			var otherINode *INode
+			inodeType, err := IdentifyType(filepath)
+			if err != nil {
+				if err != ErrNotIdenfiedType {
+					report <- statusReport{path: filepath, err: err}
+					return // Is this necessary?
+				}
+				otherINode = NewINode(-1, 0, filepath)
+			} else {
+				otherINode = NewINode(inodeType, 0, filepath)
+			}
+			i.Children.Append(otherINode)
+		}
+	}
+	report <- statusReport{path: i.Path, err: nil}
+}
+
+func (i *INode) walkDir(reportUp chan statusReport) {
+	ch := make(chan int64)
+	report := make(chan statusReport)
+	content, err := ls(i.Path)
+	if err != nil {
+		Console.Fatal(err)
+	}
+	go runThrough(i, ch, report, content)
+	for {
+		select {
+		case size := <-ch:
+			i.Size += size
+		case status := <-report:
+			if status.err != nil {
+				reportUp <- status
+			}
+			if status.path == i.Path {
+				reportUp <- status
+				close(reportUp)
+				return
+			}
+		}
+	}
+}
+
+// Sizer is the main entrypoint for the computation of the size of a directory.
+func (i *INode) Sizer() {
+	if i.Kind == FileTypes["file"] {
+		Console.Println("This is not a directory!")
+		return
+	}
+	report := make(chan statusReport)
+	i.walkDir(report)
+	for r := range report {
+		if r.err != nil {
+			Console.Fatalf("%s: %s", r.path, r.err)
+		}
+		Console.Printf("%s: %d", i.Path, i.Size)
+	}
+}