]> git.lizzy.rs Git - micro.git/blob - internal/shell/job.go
Merge pull request #1324 from konsumer/master
[micro.git] / internal / shell / job.go
1 package shell
2
3 import (
4         "bytes"
5         "io"
6         "os/exec"
7 )
8
9 var Jobs chan JobFunction
10
11 func init() {
12         Jobs = make(chan JobFunction, 100)
13 }
14
15 // Jobs are the way plugins can run processes in the background
16 // A job is simply a process that gets executed asynchronously
17 // There are callbacks for when the job exits, when the job creates stdout
18 // and when the job creates stderr
19
20 // These jobs run in a separate goroutine but the lua callbacks need to be
21 // executed in the main thread (where the Lua VM is running) so they are
22 // put into the jobs channel which gets read by the main loop
23
24 // JobFunction is a representation of a job (this data structure is what is loaded
25 // into the jobs channel)
26 type JobFunction struct {
27         Function func(string, []interface{})
28         Output   string
29         Args     []interface{}
30 }
31
32 // A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
33 type CallbackFile struct {
34         io.Writer
35
36         callback func(string, []interface{})
37         args     []interface{}
38 }
39
40 func (f *CallbackFile) Write(data []byte) (int, error) {
41         // This is either stderr or stdout
42         // In either case we create a new job function callback and put it in the jobs channel
43         jobFunc := JobFunction{f.callback, string(data), f.args}
44         Jobs <- jobFunc
45         return f.Writer.Write(data)
46 }
47
48 // JobStart starts a shell command in the background with the given callbacks
49 // It returns an *exec.Cmd as the job id
50 func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
51         return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
52 }
53
54 // JobSpawn starts a process with args in the background with the given callbacks
55 // It returns an *exec.Cmd as the job id
56 func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
57         // Set up everything correctly if the functions have been provided
58         proc := exec.Command(cmdName, cmdArgs...)
59         var outbuf bytes.Buffer
60         if onStdout != nil {
61                 proc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}
62         } else {
63                 proc.Stdout = &outbuf
64         }
65         if onStderr != nil {
66                 proc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}
67         } else {
68                 proc.Stderr = &outbuf
69         }
70
71         go func() {
72                 // Run the process in the background and create the onExit callback
73                 proc.Run()
74                 jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
75                 Jobs <- jobFunc
76         }()
77
78         return proc
79 }
80
81 // JobStop kills a job
82 func JobStop(cmd *exec.Cmd) {
83         cmd.Process.Kill()
84 }
85
86 // JobSend sends the given data into the job's stdin stream
87 func JobSend(cmd *exec.Cmd, data string) {
88         stdin, err := cmd.StdinPipe()
89         if err != nil {
90                 return
91         }
92
93         stdin.Write([]byte(data))
94 }