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  }