github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/progress/main.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "math/rand" 7 "sync" 8 "sync/atomic" 9 "time" 10 11 termbox "github.com/nsf/termbox-go" 12 ) 13 14 func init() { 15 termbox.Init() 16 termbox.SetInputMode(termbox.InputEsc) 17 } 18 19 type KeyPress struct { 20 Char rune 21 Key termbox.Key 22 } 23 24 type Repaint struct{} 25 26 var Actions = make(chan interface{}) 27 28 func Listen() { 29 go func() { 30 tick := time.NewTicker(100 * time.Millisecond) 31 defer tick.Stop() 32 for range tick.C { 33 select { 34 case Actions <- Repaint{}: 35 default: 36 } 37 } 38 }() 39 40 for { 41 switch ev := termbox.PollEvent(); ev.Type { 42 case termbox.EventKey: 43 switch ev.Key { 44 case termbox.KeyCtrlC: 45 close(Actions) 46 return 47 default: 48 Actions <- KeyPress{ev.Ch, ev.Key} 49 } 50 case termbox.EventError: 51 //TODO: proper error handling 52 panic(ev.Err) 53 } 54 } 55 } 56 57 type Process struct { 58 Name string 59 progress int32 60 done int32 61 mu sync.Mutex 62 err error 63 } 64 65 func (process *Process) Update(value int) { 66 atomic.StoreInt32(&process.progress, int32(value)) 67 } 68 69 func (process *Process) Done() { 70 atomic.StoreInt32(&process.done, 1) 71 } 72 73 func (process *Process) Progress() (progress int, done bool) { 74 progress = int(atomic.LoadInt32(&process.progress)) 75 done = atomic.LoadInt32(&process.done) == 1 76 return 77 } 78 79 func (process *Process) Fail(err error) { 80 process.mu.Lock() 81 process.err = err 82 process.mu.Unlock() 83 process.Done() 84 } 85 86 func (process *Process) Error() error { 87 process.mu.Lock() 88 err := process.err 89 process.mu.Unlock() 90 return err 91 } 92 93 type Processes struct { 94 mu sync.Mutex 95 list []*Process 96 } 97 98 func (processes *Processes) Add(process *Process) { 99 processes.mu.Lock() 100 defer processes.mu.Unlock() 101 102 processes.list = append(processes.list, process) 103 } 104 105 func (processes *Processes) Paint() { 106 termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) 107 108 processes.mu.Lock() 109 defer processes.mu.Unlock() 110 111 x, y := 0, 0 112 for _, process := range processes.list { 113 progress, done := process.Progress() 114 line := fmt.Sprintf("%-10s %4d", process.Name, progress) 115 if done { 116 line += " done" 117 } 118 119 x = 0 120 for _, r := range line { 121 termbox.SetCell(x, y, r, termbox.ColorDefault, termbox.ColorDefault) 122 x++ 123 } 124 y++ 125 } 126 127 termbox.Flush() 128 } 129 130 var Processing Processes 131 132 func ProcessFile(char rune) { 133 process := &Process{} 134 process.Name = string(char) 135 136 go func() { 137 defer process.Done() 138 139 tick := time.NewTicker(time.Millisecond * time.Duration(rand.Intn(100)+100)) 140 defer tick.Stop() 141 142 k := 0 143 for range tick.C { 144 process.Update(k) 145 k++ 146 if rand.Intn(500) == 0 { 147 process.Fail(errors.New("we got a zeeero")) 148 return 149 } 150 } 151 }() 152 153 Processing.Add(process) 154 } 155 156 func main() { 157 go Listen() 158 for ev := range Actions { 159 switch ev := ev.(type) { 160 case Repaint: 161 Processing.Paint() 162 case KeyPress: 163 ProcessFile(ev.Char) 164 Processing.Paint() 165 } 166 } 167 }