add ability for live output, add events system

This commit is contained in:
Shane C 2024-07-06 11:48:45 -04:00
parent 4e2c314c2c
commit 750686fd1f
Signed by: shane
GPG key ID: E46B5FEA35B22FF9
3 changed files with 138 additions and 16 deletions

10
linux/events.go Normal file
View file

@ -0,0 +1,10 @@
package linux
const (
EventOutput = iota
EventStderr
)
type EventOutputData struct {
Output string
}

View file

@ -2,10 +2,17 @@ package linux
import ( import (
"errors" "errors"
"io"
"sync"
) )
type LinuxCommand struct { type LinuxCommand struct {
Options CommandOptions Options CommandOptions
handlers map[int]interface{}
wg sync.WaitGroup
stdout io.ReadCloser
stderr io.ReadCloser
stdin io.WriteCloser
} }
type CommandOptions struct { type CommandOptions struct {
@ -24,4 +31,5 @@ var (
ErrRunningCmd = errors.New("error running command") ErrRunningCmd = errors.New("error running command")
ErrCommandNotFound = errors.New("error command not found") ErrCommandNotFound = errors.New("error command not found")
ErrCommandNotExecutable = errors.New("error command not executable") ErrCommandNotExecutable = errors.New("error command not executable")
ErrInvalidHandler = errors.New("invalid handler")
) )

View file

@ -1,10 +1,15 @@
package linux package linux
import ( import (
"bufio"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/sys/unix"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"strings"
"syscall"
) )
func NewCommand(options CommandOptions) (*LinuxCommand, error) { func NewCommand(options CommandOptions) (*LinuxCommand, error) {
@ -23,33 +28,67 @@ func NewCommand(options CommandOptions) (*LinuxCommand, error) {
return &LinuxCommand{ return &LinuxCommand{
Options: options, Options: options,
handlers: make(map[int]interface{}),
}, nil }, nil
} }
func (cmd *LinuxCommand) AddHandler(handler interface{}) error {
switch h := handler.(type) {
case func(data EventOutputData) error:
cmd.handlers[EventOutput] = h
break
default:
return ErrInvalidHandler
}
return nil
}
func (cmd *LinuxCommand) Run() error { func (cmd *LinuxCommand) Run() error {
var sourceCommand string var sourceCommand strings.Builder
for _, value := range cmd.Options.Sources { for _, value := range cmd.Options.Sources {
sourceCommand += fmt.Sprintf("source %s && ", value) sourceCommand.WriteString(fmt.Sprintf("source %s && ", value))
} }
command := exec.Command(cmd.Options.Shell, "-c", sourceCommand+cmd.Options.Command) var commandOptions strings.Builder
commandOptions.WriteString(" ")
for index, arg := range cmd.Options.Args {
if len(cmd.Options.Args)-1 == index {
commandOptions.WriteString(fmt.Sprintf("%s", arg))
} else {
commandOptions.WriteString(fmt.Sprintf("%s ", arg))
}
}
command := exec.Command(cmd.Options.Shell, "-c", sourceCommand.String()+cmd.Options.Command+commandOptions.String())
command.Dir = cmd.Options.Cwd command.Dir = cmd.Options.Cwd
command.Args = append(command.Args, cmd.Options.Args...)
// Loop through env to format and add them to the command. var signalChannel chan os.Signal
for key, value := range cmd.Options.Env {
command.Env = append(command.Env, fmt.Sprintf("%s=%s", key, value))
}
isCommandExecutable, err := cmd.isCommandExecutable(cmd.Options.Command) if len(cmd.handlers) != 0 {
command.SysProcAttr = &unix.SysProcAttr{Setsid: true}
signalChannel = make(chan os.Signal, 1)
signal.Notify(signalChannel, unix.SIGINT, unix.SIGTERM)
var err error
cmd.stdout, err = command.StdoutPipe()
if err != nil { if err != nil {
return err return err
} }
if !isCommandExecutable { cmd.stdin, err = command.StdinPipe()
return ErrCommandNotExecutable if err != nil {
return err
}
cmd.stderr, err = command.StderrPipe()
if err != nil {
return err
}
} }
if err := command.Start(); err != nil { if err := command.Start(); err != nil {
@ -65,6 +104,67 @@ func (cmd *LinuxCommand) Run() error {
} }
} }
cmd.wg.Add(3)
go func() {
defer cmd.wg.Done()
scanner := bufio.NewScanner(cmd.stderr)
for scanner.Scan() {
line := scanner.Text()
if h, ok := cmd.handlers[EventOutput]; ok {
if err := h.(func(data EventOutputData) error)(EventOutputData{
Output: line,
}); err != nil {
return
}
}
}
}()
go func() {
defer cmd.wg.Done()
scanner := bufio.NewScanner(cmd.stdout)
for scanner.Scan() {
line := scanner.Text()
if h, ok := cmd.handlers[EventOutput]; ok {
if err := h.(func(data EventOutputData) error)(EventOutputData{
Output: line,
}); err != nil {
return
}
}
}
}()
go func() {
defer cmd.wg.Done()
select {
case _, ok := <-signalChannel:
if !ok {
return
}
if err := unix.Kill(-command.Process.Pid, syscall.SIGINT); err != nil {
return
}
}
}()
// Loop through env to format and add them to the command.
for key, value := range cmd.Options.Env {
command.Env = append(command.Env, fmt.Sprintf("%s=%s", key, value))
}
isCommandExecutable, err := cmd.isCommandExecutable(cmd.Options.Command)
if err != nil {
return err
}
if !isCommandExecutable {
return ErrCommandNotExecutable
}
if err := command.Wait(); err != nil { if err := command.Wait(); err != nil {
var exitErr *exec.ExitError var exitErr *exec.ExitError
if errors.As(err, &exitErr) { if errors.As(err, &exitErr) {
@ -78,6 +178,10 @@ func (cmd *LinuxCommand) Run() error {
} }
} }
close(signalChannel)
signal.Stop(signalChannel)
cmd.wg.Wait()
return nil return nil
} }