summaryrefslogtreecommitdiff
path: root/lib/astal/io/process.vala
blob: 95c67a3f4286ecb6fae9eba5c0f837e1b6e365f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**
 * `Process` provides shortcuts for [[email protected]] with sane defaults.
 */
public class AstalIO.Process : Object {
    public string[] argv { construct; get; }

    private void read_stream(DataInputStream stream, bool err) {
        stream.read_line_utf8_async.begin(Priority.DEFAULT, null, (_, res) => {
            try {
                var output = stream.read_line_utf8_async.end(res);
                if (output != null) {
                    if (err) {
                        stdout(output.strip());
                    } else {
                        stderr(output.strip());
                    }
                    read_stream(stream, err);
                }
            } catch (Error err) {
                critical(err.message);
            }
        });
    }

    private DataInputStream out_stream;
    private DataInputStream err_stream;
    private DataOutputStream in_stream;
    private Subprocess process;

    /**
     * When the underlying subprocess writes to its stdout.
     *
     * @param out Line written to stdout
     */
    public signal void stdout(string out);

    /**
     * When the underlying subprocess writes to its stderr.
     *
     * @param err Line written to stderr
     */
    public signal void stderr(string err);

    /**
     * When the underlying subprocess exits or is terminated.
     *
     * @param code Exit code or signal number if terminated
     */
    public signal void exit(int code, bool terminated);

    /**
     * Force quit the subprocess.
     */
    public void kill() {
        process.force_exit();
    }

    /**
     * Send a signal to the subprocess.
     *
     * @param signal_num Signal number to be sent
     */
    public void signal(int signal_num) {
        process.send_signal(signal_num);
    }

    /**
     * Write a line to the subprocess' stdin synchronously.
     *
     * @param in String to be written to stdin
     */
    public void write(string in) throws Error {
        in_stream.put_string(in);
    }

    /**
     * Write a line to the subprocess' stdin asynchronously.
     *
     * @param in String to be written to stdin
     */
    public async void write_async(string in) {
        try {
            yield in_stream.write_all_async(in.data, in.data.length, null, null);
        } catch (Error err) {
            printerr("%s\n", err.message);
        }
    }

    /** See [[email protected]] */
    public Process(string[] cmd) throws Error {
        Object(argv: cmd);
        process = new Subprocess.newv(
            cmd,
            SubprocessFlags.STDIN_PIPE |
            SubprocessFlags.STDERR_PIPE |
            SubprocessFlags.STDOUT_PIPE
        );

        out_stream = new DataInputStream(process.get_stdout_pipe());
        err_stream = new DataInputStream(process.get_stderr_pipe());
        in_stream = new DataOutputStream(process.get_stdin_pipe());

        read_stream(out_stream, true);
        read_stream(err_stream, false);

        process.wait_async.begin(null, (_, res) => {
            try {
                process.wait_async.end(res);
            } catch (Error err) {
                // ignore
            }

            if (process.get_if_exited()) {
                exit(process.get_exit_status(), false);
            }

            if (process.get_if_signaled()) {
                exit(process.get_term_sig(), true);
            }
        });
    }

    /**
     * Start a new subprocess with the given command.
     *
     * The first element of the vector is executed with the remaining elements as the argument list.
     */
    public static Process subprocessv(string[] cmd) throws Error {
        return new Process(cmd);
    }

    /**
     * Start a new subprocess with the given command
     * which is parsed using [[email protected]_parse_argv].
     */
    public static Process subprocess(string cmd) throws Error {
        string[] argv;
        Shell.parse_argv(cmd, out argv);
        return Process.subprocessv(argv);
    }

    /**
     * Execute a command synchronously.
     * The first element of the vector is executed with the remaining elements as the argument list.
     *
     * @return stdout of the subprocess
     */
    public static string execv(string[] cmd) throws Error {
        var process = new Subprocess.newv(
            cmd,
            SubprocessFlags.STDERR_PIPE |
            SubprocessFlags.STDOUT_PIPE
        );

        string err_str, out_str;
        process.communicate_utf8(null, null, out out_str, out err_str);
        var success = process.get_successful();
        if (success) {
            return out_str.strip();
        } else {
            throw new IOError.FAILED(err_str.strip());
        }
    }

    /**
     * Execute a command synchronously.
     * The command is parsed using [[email protected]_parse_argv].
     *
     * @return stdout of the subprocess
     */
    public static string exec(string cmd) throws Error {
        string[] argv;
        Shell.parse_argv(cmd, out argv);
        return Process.execv(argv);
    }

    /**
     * Execute a command asynchronously.
     * The first element of the vector is executed with the remaining elements as the argument list.
     *
     * @return stdout of the subprocess
     */
    public static async string exec_asyncv(string[] cmd) throws Error {
        var process = new Subprocess.newv(
            cmd,
            SubprocessFlags.STDERR_PIPE |
            SubprocessFlags.STDOUT_PIPE
        );

        string err_str, out_str;
        yield process.communicate_utf8_async(null, null, out out_str, out err_str);
        var success = process.get_successful();
        if (success) {
            return out_str.strip();
        } else {
            throw new IOError.FAILED(err_str.strip());
        }
    }

    /**
     * Execute a command asynchronously.
     * The command is parsed using [[email protected]_parse_argv].
     *
     * @return stdout of the subprocess
     */
    public static async string exec_async(string cmd) throws Error {
        string[] argv;
        Shell.parse_argv(cmd, out argv);
        return yield exec_asyncv(argv);
    }
}