1
0
Fork 0
anymenu/command.go

139 lines
2.5 KiB
Go

package menu
import (
"bytes"
"encoding/json"
"log"
"os/exec"
"strconv"
"strings"
"time"
"github.com/juju/errors"
)
var errTimeout = errors.New("command timed out")
type command struct {
commandDetails
busy bool
updateInterval time.Duration
timeout time.Duration
ticker *time.Ticker
out *string
error error
}
type commandDetails struct {
ShellCommand string `json:"cmd"`
UpdateInterval string `json:"update_interval"`
Timeout string `json:"timeout"`
}
// Can be both a structure or a command string.
func (c *command) UnmarshalJSON(b []byte) error {
if len(b) > 0 && b[0] == '{' {
err := json.Unmarshal(b, &c.commandDetails)
if err != nil {
return err
}
if c.UpdateInterval != "" {
c.updateInterval, err = time.ParseDuration(c.UpdateInterval)
if err != nil {
return err
}
}
if c.Timeout != "" {
c.timeout, err = time.ParseDuration(c.Timeout)
if err != nil {
return err
}
}
return nil
}
str, err := strconv.Unquote(string(b))
if err != nil {
return err
}
c.ShellCommand = str
return nil
}
func (c *command) exec() {
c.busy = true
defer func() { c.busy = false }()
switch {
case c.ShellCommand != "":
c.out, c.error = execShellCommand(c.ShellCommand, c.timeout)
}
}
func (c *command) keepUpdated() {
c.exec()
if c.updateInterval == 0 {
return
// c.UpdateInterval = 3 * time.Second
}
c.ticker = time.NewTicker(c.updateInterval)
go func() {
for range c.ticker.C {
c.exec()
if c.error != nil {
log.Printf("Command failed: %v", c.error)
}
}
}()
}
func (c *command) resetTimer() {
if c.ticker != nil {
c.ticker.Stop()
}
c.keepUpdated()
}
func execShellCommand(shellCommand string, timeout time.Duration) (*string, error) {
log.Println("Command:", shellCommand)
var out bytes.Buffer
cmd := exec.Command("bash", "-c", shellCommand)
cmd.Stdout = &out
cmd.Stderr = &out
if err := cmd.Start(); err != nil {
return nil, err
}
if err := waitWithTimeout(cmd, timeout); err != nil {
return nil, err
}
strOut := strings.TrimSpace(out.String())
// log.Println("Output:", strOut)
return &strOut, nil
}
func waitWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
if timeout == 0 {
return cmd.Wait()
}
errCh := make(chan error, 1)
go func() {
errCh <- cmd.Wait()
}()
select {
case <-time.After(timeout):
if err := cmd.Process.Kill(); err != nil {
log.Printf("Failed to kill command after timeout: %v", err)
}
return errTimeout
case err := <-errCh:
return err
}
}