webcmd/Task.go

127 lines
2.3 KiB
Go

package webcmd
import (
"bufio"
"context"
"crypto/rand"
"encoding/base32"
"errors"
"io"
"os/exec"
"sync"
"time"
)
type OutputLine struct {
isError bool
text string
}
type Task struct {
cmd *exec.Cmd
output []OutputLine
started int64
stopped int64
exitCode int
cancel context.CancelFunc
}
func (t Task) Finished() bool {
return t.stopped != 0
}
func uuid() string {
buff := make([]byte, 15) // multiples of five are best for base32
_, err := rand.Read(buff)
if err != nil {
panic(err)
}
return base32.StdEncoding.EncodeToString(buff)
}
// LaunchTask creates a new task from the given command parameters.
func (this *App) LaunchTask(workDir string, params []string) (taskRef string, err error) {
if len(params) == 0 {
return "", errors.New("No parameters for task")
}
ref := uuid()
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, params[0], params[1:]...)
errPipe, err := cmd.StderrPipe()
if err != nil {
return "", err
}
outPipe, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
cmd.Dir = workDir
err = cmd.Start()
if err != nil {
return "", err
}
this.tasksMtx.Lock()
this.tasks[ref] = Task{
cmd: cmd,
output: make([]OutputLine, 0),
started: time.Now().Unix(),
stopped: 0,
exitCode: 0,
cancel: cancel,
}
this.tasksMtx.Unlock()
wg := sync.WaitGroup{}
wg.Add(2)
writeline := func(text string, isError bool) {
this.tasksMtx.Lock()
defer this.tasksMtx.Unlock()
task := this.tasks[ref]
task.output = append(task.output, OutputLine{isError: isError, text: text})
if len(task.output) > 2*this.cfg.MaxHistoryLines {
task.output = task.output[this.cfg.MaxHistoryLines:]
}
this.tasks[ref] = task
}
pipe2line := func(rc io.ReadCloser, isError bool) {
defer wg.Done()
sc := bufio.NewScanner(rc)
for sc.Scan() {
writeline(sc.Text(), isError)
}
rc.Close()
}
go pipe2line(errPipe, true)
go pipe2line(outPipe, false)
go func() {
wg.Wait()
err := cmd.Wait()
stopTime := time.Now().Unix()
exitCode := 0
if err != nil {
writeline(err.Error(), true)
exitCode = 1
}
this.tasksMtx.Lock()
defer this.tasksMtx.Unlock()
task := this.tasks[ref]
task.stopped = stopTime
task.exitCode = exitCode
this.tasks[ref] = task
}()
return ref, nil
}