package sizer import ( "bufio" "fmt" "io" "os" ) type dummy struct{} func (d dummy) Write(p []byte) (n int, err error) { return 0, nil } // Trivella provides an io.Writer with a notion of depth in the // path graph type Trivella struct { Level uint Writer io.Writer } // Scendi increases the depth in the graph func (t *Trivella) Scendi() *Trivella { var v *Trivella if t.Level > 0 { v = TrInit(t.Writer, t.Level-1) } else { var d dummy v = TrInit(d, 0) } return v } // TrInit assigns the writer to a Trivella func TrInit(writer io.Writer, level uint) *Trivella { return &Trivella{Writer: writer, Level: level} } // Ruspa is the asynchronous worker that is in charge of collecting // the underlying sizes and relay the total to the eventual parent. type Ruspa struct { INode *INode ReportIn chan StatusReport ReportOut chan StatusReport DoneIn chan StatusReport DoneOut chan StatusReport Opened map[string]*Ruspa } // NewRuspa inits a new Ruspa func NewRuspa(inode *INode, reportOut, doneOut chan StatusReport) *Ruspa { var r Ruspa r.INode = inode r.ReportOut = reportOut r.DoneOut = doneOut r.ReportIn = make(chan StatusReport) r.DoneIn = make(chan StatusReport) r.Opened = make(map[string]*Ruspa) return &r } // Ammucchia gathers the size of the underlying INode(s) from the Ruspa worker // and adds to the INode size. func (w *Ruspa) Ammucchia(t *Trivella) { Console.Debugln(Gray("Ammucchia:", w.INode.Path)) for len(w.Opened) > 0 { select { case report := <-w.ReportIn: Console.Debugln(Gray("ReportIn:", report)) if report.err == nil { w.INode.Size += report.size fmt.Fprintf(t.Writer, "%s: %d\n", w.INode.Path, w.INode.Size) } else { Console.Debugln(Red(report.err)) } case leaf := <-w.DoneIn: Console.Debugln(Gray("Done:", leaf.path)) delete(w.Opened, leaf.path) } } Console.Debugln(Gray(w.INode.Path, ": Completed. Exiting...")) w.DoneOut <- StatusReport{path: w.INode.Path, size: w.INode.Size} } // Scava starts a worker on the given INode. func (w *Ruspa) Scava(t *Trivella) { Console.Debugln(Gray("Scava: ", w.INode.Path)) children, err := ls(w.INode.Path) if err != nil { w.ReportOut <- StatusReport{path: w.INode.Path, err: err} } for _, childPath := range children { kind, size, err := IdentifyType(childPath) if err != nil { w.ReportOut <- StatusReport{path: childPath, err: err} } childINode := NewINode(kind, size, childPath) w.INode.Children.Append(childINode) switch { case kind == FileType: Console.Debugln(Gray("[file]", w.INode.Path, " ~> ", childINode.Path)) w.ReportOut <- StatusReport{path: childPath, size: size} case kind == DirType: Console.Debugln(Gray("[dir]", w.INode.Path, " ~> ", childINode.Path)) w.INode.Size += size cw := NewRuspa(childINode, w.ReportIn, w.DoneIn) w.Opened[childPath] = cw go cw.Scava(t.Scendi()) } } if len(w.Opened) != 0 { Console.Debugln(Gray("Spawning Ammucchia:", w.INode.Path)) go w.Ammucchia(t) } else { w.DoneOut <- StatusReport{path: w.INode.Path, size: w.INode.Size} } } // Pesa starts the worker for current inode and returns the channel // to listen to get the size. func (i *INode) Pesa(t *Trivella, report, done chan StatusReport) { w := NewRuspa(i, report, done) go w.Scava(t) } // NastroConvogliatore starts a Pesa on a given path and displays // the current value of the size. func NastroConvogliatore(path string, depth uint) { finished := false pathINode, err := INodeFromPath(path) if err != nil { Console.Fatalln("Failed creating inode:", err) } Console.Debugln(Gray("Starting Pesa on path:", path)) report := make(chan StatusReport) done := make(chan StatusReport) writer := bufio.NewWriter(os.Stdout) defer writer.Flush() t := TrInit(writer, depth) fmt.Fprintf(writer, "%s\n", Green("Starting...")) pathINode.Pesa(t, report, done) for !finished { select { case res := <-report: Console.Debugln(Gray("MAIN: result =>", res)) case res := <-done: Console.Debugln(Gray("MAIN: done =>", res)) finished = true } } fmt.Fprintf(writer, "%s: %d\n", path, pathINode.Size) return }