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