1 ///<script type="text/javascript">
3 var Gio = imports.gi.Gio;
4 var GLib = imports.gi.GLib;
10 * Library to wrap GLib.spawn_async_with_pipes
14 * Spawn = import.Spawn;
17 * var output = Spawn.run({
19 * args : [ 'ls', '-l' ],
20 * env : [], // optional
22 output : function (line) { Seed.print(line); },
23 * stderr : function (line) {Seed.print("ERROR" + line); },
24 * input : function() { return 'xxx' },
30 * CRITICAL - needs this change to gir in GLib-2.0.gir g_spawn_async_with_pipes
32 <parameter name="argv" transfer-ownership="none">
33 <array c:type="gchar**">
37 <parameter name="envp" transfer-ownership="none" allow-none="1">
38 <array c:type="gchar**">
44 *<method name="read_line"
45 c:identifier="g_io_channel_read_line"
47 <return-value transfer-ownership="none">
48 <type name="IOStatus" c:type="GIOStatus"/>
51 <parameter name="str_return" transfer-ownership="full" direction="out">
52 <type name="utf8" c:type="gchar**"/>
54 <parameter name="length" transfer-ownership="none" direction="out">
55 <type name="gsize" c:type="gsize*"/>
57 <parameter name="terminator_pos" transfer-ownership="none" direction="out">
58 <type name="gsize" c:type="gsize*"/>
71 * @param cfg {Object} settings - see properties.
73 * @arg cwd {String} working directory. (defaults to home directory)
74 * @arg args {Array} arguments eg. [ 'ls', '-l' ]
75 * @arg listeners {Object} (optional) handlers for output, stderr, input
76 * stderr/output both receive output line as argument
77 * input should return any standard input
78 * finish recieves result as argument.
79 * @arg env {Array} enviroment eg. [ 'GITDIR=/home/test' ]
80 * @arg async {Boolean} (optional)return instantly, or wait for exit. (default no)
81 * @arg exceptions {Boolean} throw exception on failure (default no)
82 * @arg debug {Boolean} print out what's going on.. (default no)
90 this.listeners = this.listeners || {};
91 this.cwd = this.cwd || GLib.get_home_dir();
92 if (!this.args || !this.args.length) {
109 * @property output {String} resulting output
113 * @property stderr {String} resulting output from stderr
117 * @property result {Number} execution result.
121 * @property pid {Number} pid of child process (of false if it's not running)
128 * @property in_ch {GLib.IOChannel} input io channel
132 * @property out_ch {GLib.IOChannel} output io channel
136 * @property err_ch {GLib.IOChannel} stderr io channel
143 * Run the configured command.
159 print("cd " + this.cwd +";" + this.args.join(" "));
162 GLib.spawn_async_with_pipes(this.cwd, this.args, this.env,
163 GLib.SpawnFlags.DO_NOT_REAP_CHILD + GLib.SpawnFlags.SEARCH_PATH ,
166 print(JSON.stringify(ret));
167 this.pid = ret.child_pid;
170 print("PID: " + this.pid);
176 GLib.spawn_close_pid(_this.pid); // hopefully kills it..
179 if (_this.in_ch) _this.in_ch.close();
180 if (_this.out_ch) _this.out_ch.close();
181 if (_this.err_ch) _this.err_ch.close();
182 // blank out channels
184 _this.err_ch = false;
185 _this.out_ch = false;
186 // rmeove listeners !! important otherwise we kill the CPU
187 if (err_src !== false) GLib.source_remove(err_src);
188 if (out_src !== false) GLib.source_remove(out_src);
195 this.in_ch = GLib.io_channel_unix_new(ret.standard_input);
196 this.out_ch = GLib.io_channel_unix_new(ret.standard_output);
197 this.err_ch = GLib.io_channel_unix_new(ret.standard_error);
199 // make everything non-blocking!
200 this.in_ch.set_flags (GLib.IOFlags.NONBLOCK);
201 this.out_ch.set_flags (GLib.IOFlags.NONBLOCK);
202 this.err_ch.set_flags (GLib.IOFlags.NONBLOCK);
205 GLib.child_watch_add(GLib.PRIORITY_DEFAULT, this.pid, function(pid, result) {
206 _this.result = result;
208 print("child_watch_add : result: " + result);
210 _this.read(_this.out_ch);
211 _this.read(_this.err_ch);
213 GLib.spawn_close_pid(_this.pid);
219 if (_this.listeners.finish) {
220 _this.listeners.finish.call(this, _this.result);
228 // add handlers for output and stderr.
229 out_src= GLib.io_add_watch(this.out_ch, GLib.PRIORITY_DEFAULT,
230 GLib.IOCondition.OUT + GLib.IOCondition.IN + GLib.IOCondition.PRI, function()
232 _this.read(_this.out_ch);
235 err_src= GLib.io_add_watch(this.err_ch, GLib.PRIORITY_DEFAULT,
236 GLib.IOCondition.ERR + GLib.IOCondition.IN + GLib.IOCondition.PRI + GLib.IOCondition.OUT,
239 _this.read(_this.err_ch);
246 if (this.pid !== false) {
247 // child can exit before we get this far..
248 if (this.listeners.input) {
250 this.write(this.listeners.input.call(this));
259 // async - if running - return..
260 if (this.async && this.pid) {
265 // start mainloop if not async..
267 if (this.pid !== false) {
269 print("starting main loop");
272 ctx = new GLib.MainLoop.c_new (null, false);
273 ctx.run(false); // wait fore exit?
276 tidyup(); // tidyup get's called in main loop.
279 if (this.exceptions && this.result != 0) {
280 this.toString = function() { return this.stderr; };
281 throw this; // we throw self...
284 // finally throw, or return self..
290 * write to stdin of process
291 * @arg str {String} string to write to stdin of process
292 * @returns GLib.IOStatus (0 == error, 1= NORMAL)
294 write : function(str) // write a line to
297 return 0; // input is closed
300 var res = this.in_ch.write_chars(str, str.length);
301 if (res != GLib.IOStatus.NORMAL) {
302 throw "Write failed";
304 return ret.bytes_written;
309 * read from pipe and call appropriate listerner and add to output or stderr string.
310 * @arg giochannel to read from.
315 var prop = ch == this.out_ch ? 'output' : 'stderr';
316 print("prop: " + prop);
319 //print(JSON.stringify(ch, null,4));
322 var status = ch.read_line(x);
323 //print("READ LINE STRING STATUS: " + status);
324 //print("got: " + JSON.stringify(x, null,4));
327 case GLib.IOStatus.NORMAL:
330 if (this.listeners[prop]) {
331 this.listeners[prop].call(this, x.str_return);
333 _this[prop] += x.str_return;
335 print(prop + ':' + x.str_return);
338 case GLib.IOStatus.AGAIN:
340 case GLib.IOStatus.ERROR:
341 case GLib.IOStatus.EOF:
353 * simple run a process - returns result, or throws stderr result...
354 * @param cfg {Object} see spawn
355 * @return {string} stdout output.
358 cfg.exceptions = true;
360 var s = new Spawn(cfg);
368 args: ['ls', '/tmp'],
371 } catch (e) { print(JSON.stringify(e)); }
373 var secs = (new Date()).getSeconds()
377 args: ['/bin/touch', '/tmp/spawntest-' + secs ],
380 } catch (e) { print( 'Error: ' + JSON.stringify(e)); }