This commit is contained in:
Blallo 2019-03-06 15:03:21 +01:00
commit 07ab12b271
No known key found for this signature in database
GPG key ID: 0CBE577C9B72DC3F
4 changed files with 277 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/tests

18
main.go Normal file
View file

@ -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
sizer/logger.go Normal file
View file

@ -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
sizer/main.go Normal file
View file

@ -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)
}
}