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 ) // 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 sizes and infos on the inodes. var INodeChan chan *INode // ErrNotIdenfiedType is the error returned when the a classifier does not // recognize the INode type. 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 FileType. func IdentifyType(path string) (int, int64, error) { f, err := os.Lstat(path) if err != nil { return -1, 0, err } switch mode := f.Mode(); { case mode.IsDir(): return DirType, f.Size(), nil case mode.IsRegular(): return FileType, f.Size(), nil } return -1, 0, 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, 1) report := make(chan statusReport, 1) 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: Console.Debugf("[%s] Report from: %s\n", i.Path, status.path) if status.err != nil { Console.Debugln("Sending error. Path:", status.path, "- Err:", status.err) reportUp <- status } if status.err == nil { Console.Debugf("[%s] Received -> %s - Closing channel...\n", i.Path, status.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) go 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) } }