src/Spawn.vala
[app.Builder.js] / src / Spawn.vala
1
2 /// # valac  --pkg gio-2.0    --pkg posix Spawn.vala -o /tmp/Spawn
3
4 using GLib;
5  
6
7 /**
8  * Revised version?
9  * 
10  * x = new Spawn( working dir, args)
11  * 
12  * // these are optionall..
13  * x.env = ..... (if you need to set one...
14  * x.output_line.connect((string str) => { ... });
15  * x.input_line.connect(() => { return string });
16  * 
17  * x.run((int res, string output, string stderr) => { ... });
18
19  * 
20  * 
21  */
22
23
24  
25 public errordomain SpawnError {
26     NO_ARGS,
27     WRITE_ERROR,
28     EXECUTE_ERROR
29
30 }
31
32 /**
33  * @class Spawn
34  * @param cwd {String}            working directory. (defaults to home directory)
35  * @param args {Array}            arguments eg. [ 'ls', '-l' ]
36  * 
37  
38  * @arg env {Array}             enviroment eg. [ 'GITDIR=/home/test' ]
39  * @arg is_async {Boolean} (optional)return instantly, or wait for exit. (default no)
40  * @arg trhow_exceptions {Boolean}    throw exception on failure (default no)
41  
42  * 
43  */
44  
45  
46 delegate void SpawnFinish (int res, string str, string stderr);
47
48 public class Spawn : Object
49 {
50         /**
51          * @signal input called at start to send input when process starts?
52          * @return the string or null 
53          */
54         public signal string? input();
55         /**
56          * @signal output_line called when a line is recieved from the process.
57          * Note you may want to connect this and run 
58          *   if ( Gtk.events_pending()) { Gtk.main_iteration(); }
59          * 
60          * @param {string} str 
61          */
62     public signal void output_line(string str);
63     
64         public string cwd;
65         public string[] args;
66         public string[] env;
67         
68         public bool is_async = true;
69         public bool throw_exceptions = false;
70
71     public Spawn(string cwd, string[] args) throws Error
72     {
73        
74      
75         this.cwd = cwd;
76         this.args = args;
77         this.env = {};
78         
79         this.output = "";
80         this.stderr = "";
81     
82         this.cwd =  this.cwd.length  < 1 ? GLib.Environment.get_home_dir() : this.cwd;
83         if (this.args.length < 0) {
84             throw new SpawnError.NO_ARGS("No arguments");
85         }
86         
87     
88     }
89
90     
91     MainLoop ctx = null; // the mainloop ctx.
92     
93     /**
94      * @property output {String} resulting output
95      */
96     public string output;
97     /**
98      * @property stderr {String} resulting output from stderr
99      */
100     public string stderr;
101      /**
102      * @property result {Number} execution result.
103      */
104     public int result= 0;
105     /**
106      * @property pid {Number} pid of child process (of false if it's not running)
107      */
108     int  pid = -1;
109     /**
110      * @property in_ch {GLib.IOChannel} input io channel
111      */
112     IOChannel in_ch = null;
113     /**
114      * @property out_ch {GLib.IOChannel} output io channel
115      */
116     IOChannel out_ch = null;
117     /**
118      * @property err_ch {GLib.IOChannel} stderr io channel
119      */
120     IOChannel err_ch = null;
121     /**
122      * @property err_src {int} the watch for errors
123      */
124     
125     int err_src = -1;
126       /**
127      * @property err_src {int} the watch for output
128      */
129     int out_src = -1;
130     
131     /**
132      * 
133      * @method run
134      * Run the configured command.
135      * result is applied to object properties (eg. '?' or 'stderr')
136      * @returns {Object} self.
137      */
138         public void run(SpawnFinish finish) throws SpawnError, GLib.SpawnError, GLib.IOChannelError
139         {
140                 
141                  
142                 err_src = -1;
143                 out_src = -1;
144                 int standard_input;
145                 int standard_output;
146                 int standard_error;
147
148
149                  
150                 GLib.debug("cd %s; %s" , this.cwd , string.joinv(" ", this.args));
151                 
152                 
153                 Process.spawn_async_with_pipes (
154                                 this.cwd,
155                                 this.args,
156                                 this.env,
157                                 SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
158                                 null,
159                                 out this.pid,
160                                 out standard_input,
161                                 out standard_output,
162                                 out standard_error);
163
164                 // stdout:
165
166                         
167                 //print(JSON.stringify(gret));    
168                  
169                 GLib.debug("PID: %d" ,this.pid);
170                  
171                 
172                 this.in_ch = new GLib.IOChannel.unix_new(standard_input);
173                 this.out_ch = new GLib.IOChannel.unix_new(standard_output);
174                 this.err_ch = new GLib.IOChannel.unix_new(standard_error);
175                 
176                 // make everything non-blocking!
177
178
179
180                           // using NONBLOCKING only works if io_add_watch
181                 //returns true/false in right conditions
182                 this.in_ch.set_flags (GLib.IOFlags.NONBLOCK);
183                 this.out_ch.set_flags (GLib.IOFlags.NONBLOCK);
184                 this.err_ch.set_flags (GLib.IOFlags.NONBLOCK);
185                            
186
187
188  
189                 ChildWatch.add (this.pid, (w_pid, result) => {
190                 
191                         this.result = result;
192                         GLib.debug("child_watch_add : result:%d ", result);
193                         
194                    
195                         this.read(this.out_ch);
196                         this.read(this.err_ch);
197                         
198                         
199                         Process.close_pid(this.pid);
200                         this.pid = -1;
201                         if (this.ctx != null) {
202                                 this.ctx.quit();
203                                 this.ctx = null;
204                         }
205                         this.tidyup();
206                         //print("DONE TIDYUP");
207                         
208                         finish(this.result, this.output, this.stderr);
209                         
210                 });
211             
212                           
213         
214         
215        
216             
217             // add handlers for output and stderr.
218         
219         this.out_src = (int) this.out_ch.add_watch (
220             IOCondition.OUT | IOCondition.IN  | IOCondition.PRI |  IOCondition.HUP |  IOCondition.ERR  ,
221             (channel, condition) => {
222                return this.read(this.out_ch);
223             }
224         );
225         this.err_src = (int) this.err_ch.add_watch (
226                  IOCondition.OUT | IOCondition.IN  | IOCondition.PRI |  IOCondition.HUP |  IOCondition.ERR  ,
227             (channel, condition) => {
228                return this.read(this.err_ch);
229             }
230         );
231               
232         
233         // call input.. 
234         if (this.pid > -1) {
235             // child can exit before we get this far..
236             var input = this.input();
237             if (input != null) {
238                         
239                 try {
240                     this.write(input);
241                      // this probably needs to be a bit smarter...
242                     //but... let's close input now..
243                     this.in_ch.shutdown(true);
244                     this.in_ch = null;
245                      
246                     
247                 } catch (Error e) {
248                     this.tidyup();
249                     return;
250                   //  throw e;
251                     
252                 }
253                 
254             }
255             
256         }
257         // async - if running - return..
258         if (this.is_async && this.pid > -1) {
259             return;
260         }
261          
262         // start mainloop if not async..
263         
264         if (this.pid > -1) {
265             GLib.debug("starting main loop");
266              //if (this.cfg.debug) {
267              //  
268              // }
269                 this.ctx = new MainLoop ();
270             this.ctx.run(); // wait fore exit?
271             
272             GLib.debug("main_loop done!");
273         } else {
274             this.tidyup(); // tidyup get's called in main loop. 
275         }
276         
277         if (this.throw_exceptions && this.result != 0) {
278             
279             throw new SpawnError.EXECUTE_ERROR(this.stderr);
280             //this.toString = function() { return this.stderr; };
281             ///throw new Exception this; // we throw self...
282         }
283         
284         // finally throw, or return self..
285         
286         return;
287     
288     }
289     
290     
291
292     private void tidyup()
293     {
294         if (this.pid > -1) {
295             Process.close_pid(this.pid); // hopefully kills it..
296             this.pid = -1;
297         }
298         try {
299             if (this.in_ch != null)  this.in_ch.shutdown(true);
300             if (this.out_ch != null)  this.out_ch.shutdown(true);
301             if (this.err_ch != null)  this.err_ch.shutdown(true);
302         } catch (Error e) {
303             // error shutting donw.
304         }
305         // blank out channels
306         this.in_ch = null;
307         this.err_ch = null;
308         this.out_ch = null;
309         // rmeove listeners !! important otherwise we kill the CPU
310         //if (this.err_src > -1 ) GLib.source_remove(this.err_src);
311         //if (this.out_src > -1 ) GLib.source_remove(this.out_src);
312         this.err_src = -1;
313         this.out_src = -1;
314         
315     }
316     
317     
318     /**
319      * write to stdin of process
320      * @arg str {String} string to write to stdin of process
321      * @returns GLib.IOStatus (0 == error, 1= NORMAL)
322      */
323     private int write(string str) throws Error // write a line to 
324     {
325         if (this.in_ch == null) {
326             return 0; // input is closed
327         }
328         //print("write: " + str);
329         // NEEDS GIR FIX! for return value.. let's ignore for the time being..
330         //var ret = {};
331         size_t written;
332         var res = this.in_ch.write_chars(str.to_utf8(), out written);
333         
334         //print("write_char retunred:" + JSON.stringify(res) +  ' ' +JSON.stringify(ret)  );
335         
336         if (res != GLib.IOStatus.NORMAL) {
337             throw new SpawnError.WRITE_ERROR("Write failed");
338         }
339         //return ret.value;
340         return str.length;
341         
342     }
343
344
345     
346     /**
347      * read from pipe and call appropriate listerner and add to output or stderr string.
348      * @arg giochannel to read from.
349      * @returns none
350      */
351     private bool read(IOChannel ch) 
352     {
353         string prop = (ch == this.out_ch) ? "output" : "stderr";
354        // print("prop: " + prop);
355
356         
357         //print(JSON.stringify(ch, null,4));
358         while (true) {
359             string buffer;
360             size_t term_pos;
361             size_t len;
362             IOStatus status;
363             try {
364                 status = ch.read_line( out buffer,  out len,  out term_pos );
365             } catch (Error e) {
366                 //FIXme
367                 break; // ??
368                 
369             }
370
371             // print('status: '  +JSON.stringify(status));
372             // print(JSON.stringify(x));
373              switch(status) {
374                 case GLib.IOStatus.NORMAL:
375                 
376                     //write(fn, x.str);
377                     
378                     //if (this.listeners[prop]) {
379                     //    this.listeners[prop].call(this, x.str_return);
380                     //}
381                     if (ch == this.out_ch) {
382                         this.output += buffer;
383                         this.output_line(  buffer);                  
384                         
385                     } else {
386                         this.stderr += buffer;
387                     }
388                     //_this[prop] += x.str_return;
389                     //if (this.cfg.debug) {
390                         GLib.debug("%s : %s", prop , buffer);
391                     //}
392                     if (this.is_async) {
393                          
394                         //if ( Gtk.events_pending()) {
395                         //     Gtk.main_iteration();
396                         //}
397                          
398                     }
399                     
400                     //this.ctx.iteration(true);
401                    continue;
402                 case GLib.IOStatus.AGAIN:
403                                         //print("Should be called again.. waiting for more data..");
404                             return true;
405                     //break;
406                 case GLib.IOStatus.ERROR:    
407                 case GLib.IOStatus.EOF:
408                             return false;
409                     //break;
410                 
411             }
412             break;
413         }
414        
415         //print("RETURNING");
416          return false; // allow it to be called again..
417     }
418     
419 }
420  
421 int main (string[] args) {
422         GLib.Log.set_handler(null, 
423                 GLib.LogLevelFlags.LEVEL_DEBUG | GLib.LogLevelFlags.LEVEL_WARNING, 
424                 (dom, lvl, msg) => {
425                 print("%s: %s\n", dom, msg);
426         });
427
428         var ctx = new GLib.MainLoop ();
429         var a = new Spawn("", { "ls" , "-l"});
430         a.run((res, str, stderr) => {
431                 print(str);
432                 ctx.quit();
433         });
434         
435         
436         ctx.run(); // wait for exit?
437             
438         return 0;
439 }
440