X-Git-Url: http://git.roojs.org/?a=blobdiff_plain;f=Spawn.vala;h=738ea44d6721d406cb253e35dc852d1d74a3d3ac;hb=0b86050bdbbbd2985f7078ca9036be0196455398;hp=ca12a0b235c281d9301b712cfe9a8384cb7f4b01;hpb=33526b160231757b195b1826da70b0f7d57d7726;p=gitlive diff --git a/Spawn.vala b/Spawn.vala index ca12a0b2..738ea44d 100644 --- a/Spawn.vala +++ b/Spawn.vala @@ -1,17 +1,41 @@ +/// # valac --pkg gio-2.0 --pkg gtk+-3.0 --pkg posix Spawn.vala -o /tmp/Spawn -using Gee; // for array list? +using GLib; +using Gtk; +// compile valac + + +///using Gee; // for array list? +/* static int main (string[] args) { // A reference to our file - var file = File.new_for_path ("data.txt"); - var m = new Monitor(); + + var cfg = new SpawnConfig("", { "ls" } , { "" }); + cfg.setHandlers( + (line) => { + stdout.printf("%s\n", line); + }, + null,null,null ); + cfg.setOptions( + false, // async + false, // exceptions?? needed?? + false // debug??? + ); + try { + new Spawn(cfg); + + } catch (Error e) { + stdout.printf("Error %s", e.message); + } + return 0; } - -var Gio = imports.gi.Gio; -var GLib = imports.gi.GLib; +*/ +//var Gio = imports.gi.Gio; +//var GLib = imports.gi.GLib; /** @@ -20,26 +44,9 @@ var GLib = imports.gi.GLib; * Library to wrap GLib.spawn_async_with_pipes * * usage: -* -* Spawn = import.Spawn; -* -* simple version.. -* var output = Spawn.run({ -* cwd : '/home', -* args : [ 'ls', '-l' ], -* env : [], // optional -* listeners : { - output : function (line) { Seed.print(line); }, -* stderr : function (line) {Seed.print("ERROR" + line); }, -* input : function() { return 'xxx' }, -* } -* }); -* -* +* v * -* -* -*var output = Spawn.run( SpawnConfig() { +*var output = new Spawn( SpawnConfig() { cwd = "/home", // empty string to default to homedirectory. args = {"ls", "-l" }, evn = {}, @@ -50,37 +57,65 @@ var GLib = imports.gi.GLib; * * */ -delegate void SpawnOutput(string line); -delegate void SpawnErr(string line); -delegate string SpawnInput(); - - +public delegate void SpawnOutput(string line); +public delegate void SpawnErr(string line); +public delegate string SpawnInput(); +public delegate void SpawnFinish(int result, string output); + -struct SpawnConfig -struct SpawnConfig { +public class SpawnConfig { public string cwd; public string[] args; public string[] env; - public boolean async; - public boolean exceptions; // fire exceptions. - public boolean debug; // fire exceptions. + public bool async; + public bool exceptions; // fire exceptions. + public bool debug; // fire exceptions. - public SpawnOutput output + public SpawnOutput output; public SpawnErr stderr; public SpawnInput input; + // defaults.. - public SpawnConfig() { - cwd = ""; - args = []; - env = []; - async = false; - exceptions = false; - debug = false; - output = null; - stderr = null; - input = null; - + public SpawnConfig(string cwd, + string[] args, + string[] env + ) { + this.cwd = cwd; + this.args = args; + this.env = env; + + async = false; + exceptions = true; + debug = false; + + output = null; + stderr = null; + input = null; + } + + + + public void setHandlers( + SpawnOutput? output, + SpawnErr? stderr, + SpawnInput? input + + ) { + this.output = output; + this.stderr = stderr; + this.input = input; + + } + + +} + +public errordomain SpawnError { + NO_ARGS, + WRITE_ERROR, + EXECUTE_ERROR + } /** @@ -104,33 +139,37 @@ struct SpawnConfig { public class Spawn : Object { + SpawnConfig cfg; - - public Spawn(SpawnConfig cfg) + public Spawn(SpawnConfig cfg) throws Error { this.cfg = cfg; - + this.output = ""; + this.stderr = ""; - this.cwd = this.cfg.cwd.length || GLib.get_home_dir(); - if (!this.cfg.args.length) { - throw "No arguments"; - } + this.cfg.cwd = this.cfg.cwd.length < 1 ? GLib.Environment.get_home_dir() : this.cfg.cwd; + if (this.cfg.args.length < 0) { + throw new SpawnError.NO_ARGS("No arguments"); + } + if (!this.cfg.async) { + this.run((res, output) => { }); + } } - boolean ctx = false; // the mainloop ctx. + MainLoop ctx = null; // the mainloop ctx. /** * @property output {String} resulting output */ - string output = ""; + public string output; /** * @property stderr {String} resulting output from stderr */ - string stderr = ""; + public string stderr; /** * @property result {Number} execution result. */ @@ -161,40 +200,42 @@ public class Spawn : Object */ int out_src = -1; + + unowned SpawnFinish on_finished; + /** * * @method run * Run the configured command. - * result is applied to object properties (eg. 'output' or 'stderr') + * result is applied to object properties (eg. '?' or 'stderr') * @returns {Object} self. */ - public void run() + public void run( SpawnFinish finished_cb) throws SpawnError, GLib.SpawnError, GLib.IOChannelError { - - var err_src = false; - var out_src = false; - int standard_input; - int standard_output; - int standard_error; + this.on_finished = finished_cb; + err_src = -1; + out_src = -1; + int standard_input; + int standard_output; + int standard_error; - var ret = {}; if (this.cfg.debug) { - print("cd " + this.cfg.cwd +";" + string.joinv(" ", this.cfg.args)); + stdout.printf("cd %s; %s\n" , this.cfg.cwd , string.joinv(" ", this.cfg.args)); } - Process.spawn_async_with_pipes ( - this.cfg.cwd, - this.cfg.args, - this.cfg.env, - SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, - null, - out this.pid, - out standard_input, - out standard_output, - out standard_error); + Process.spawn_async_with_pipes ( + this.cfg.cwd, + this.cfg.args, + this.cfg.env, + SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, + null, + out this.pid, + out standard_input, + out standard_output, + out standard_error); // stdout: @@ -202,139 +243,168 @@ public class Spawn : Object //print(JSON.stringify(gret)); if (this.cfg.debug) { - print("PID: " + this.pid); - } - - ChildWatch.add (this.pid, (w_pid, result) => { - - this.result = result; - if (_this.debug) { - print("child_watch_add : result: " + result); - } - - this.read(this.out_ch); - this.read(this.err_ch); - - Process.close_pid(this.pid); - this.pid = -1; - if (this.ctx) { - this.ctx.quit(); - } - this.tidyup(); - //print("DONE TIDYUP"); - if (this.cfg.finish) { - this.cfg.finish(this.result); - } - }); - - + stdout.printf("PID: %d\n" ,this.pid); + } + this.ref(); // additional ref - cleared on tidyup... - this.in_ch = new GLib.IOChannel.unix_new(ret.standard_input); - this.out_ch = new GLib.IOChannel.unix_new(ret.standard_output); - this.err_ch = new GLib.IOChannel.unix_new(ret.standard_error); + this.in_ch = new GLib.IOChannel.unix_new(standard_input); + this.out_ch = new GLib.IOChannel.unix_new(standard_output); + this.err_ch = new GLib.IOChannel.unix_new(standard_error); // make everything non-blocking! + + // using NONBLOCKING only works if io_add_watch + //returns true/false in right conditions + this.in_ch.set_flags (GLib.IOFlags.NONBLOCK); + this.out_ch.set_flags (GLib.IOFlags.NONBLOCK); + this.err_ch.set_flags (GLib.IOFlags.NONBLOCK); + - // using NONBLOCKING only works if io_add_watch - //returns true/false in right conditions - this.in_ch.set_flags (GLib.IOFlags.NONBLOCK); - this.out_ch.set_flags (GLib.IOFlags.NONBLOCK); - this.err_ch.set_flags (GLib.IOFlags.NONBLOCK); - - - // add handlers for output and stderr. - - this.out_src = this.out_ch.add_watch ( - IOCondition.OUT | IOCondition.IN | IOCondition.PRI | IOCondition.HUP | IOCondition.ERR , - (channel, condition) => { - return this.read(_this.out_ch); - } - ); - this.err_src = this.err_ch.add_watch ( - IOCondition.OUT | IOCondition.IN | IOCondition.PRI | IOCondition.HUP | IOCondition.ERR , - (channel, condition) => { - return this.read(_this.err_ch); - } - ); - + + ChildWatch.add (this.pid, this.on_child_watch); + + + + + + + // add handlers for output and stderr. + + this.out_src = (int) this.out_ch.add_watch ( + IOCondition.OUT | IOCondition.IN | IOCondition.PRI | IOCondition.HUP | IOCondition.ERR , + (channel, condition) => { + return this.read(channel); + //return this.out_ch != null ? this.read(this.out_ch) : true; + } + ); + this.err_src = (int) this.err_ch.add_watch ( + IOCondition.OUT | IOCondition.IN | IOCondition.PRI | IOCondition.HUP | IOCondition.ERR , + (channel, condition) => { + return this.read(channel); + //return this.err_ch != null ? this.read(this.err_ch) : true; + } + ); + // call input.. if (this.pid > -1) { - // child can exit before 1we get this far.. + // child can exit before we get this far.. if (this.cfg.input != null) { - if (this.cfg.debug) print("Trying to call listeners"); + if (this.cfg.debug) print("Trying to call listeners"); try { this.write(this.cfg.input()); - // this probably needs to be a bit smarter... - //but... let's close input now.. - this.in_ch.close(); - this.in_ch = -1; - - - } catch (e) { + // this probably needs to be a bit smarter... + //but... let's close input now.. + this.in_ch.shutdown(true); + this.in_ch = null; + + + } catch (Error e) { this.tidyup(); - throw e; + return; + // throw e; } } + } - // async - if running - return.. + // async - if running - return.. if (this.cfg.async && this.pid > -1) { + //this.ref(); return; } // start mainloop if not async.. if (this.pid > -1) { - if (this.cfg.debug) { - print("starting main loop"); - } - this.ctx = new MainLoop (); - loop.run(); // wait fore exit? + //print("starting main loop"); + //if (this.cfg.debug) { + // + // } + this.ctx = new MainLoop (); + this.ctx.run(); // wait fore exit? //print("main_loop done!"); } else { this.tidyup(); // tidyup get's called in main loop. } + if (this.cfg.exceptions && this.result != 0) { + var errstr = string.joinv(" ", this.cfg.args) + "\n"; + errstr += this.output; + errstr += this.output.length > 0 ? "\n" : ""; + errstr += this.stderr; + //print("Throwing execute error:%s\n", errstr); + throw new SpawnError.EXECUTE_ERROR(errstr); //this.toString = function() { return this.stderr; }; ///throw new Exception this; // we throw self... } - // finally throw, or return self.. - return; } + void on_child_watch(GLib.Pid w_pid, int result) { + + this.result = result; + if (this.cfg.debug) { + stdout.printf("child_watch_add : result:%d\n", result); + } + + this.read(this.out_ch); + this.read(this.err_ch); + + + Process.close_pid(this.pid); + this.pid = -1; + if (this.ctx != null) { + this.ctx.quit(); + this.ctx = null; + + } + //print("child process done - running callback, then tidyup"); + this.on_finished(this.result, this.output + (this.output.length > 0 ? "\n" : "") + this.stderr); + // this.unref(); + this.tidyup(); + + //print("DONE TIDYUP"); + + + } + private void tidyup() { - if (this.pid > -1) { - Process.close_pid(this.pid); // hopefully kills it.. - this.pid = -1; - } - if (this.in_ch) this.in_ch.close(); - if (this.out_ch) this.out_ch.close(); - if (this.err_ch) this.err_ch.close(); - // blank out channels - this.in_ch = false; - this.err_ch = false; - this.out_ch = false; - // rmeove listeners !! important otherwise we kill the CPU - if (this.err_src !== false) GLib.source_remove(this.err_src); - if (this.out_src !== false) GLib.source_remove(this.out_src); - this.err_src = false; - this.out_src = false; - + //print("Tidyup\n"); + if (this.pid > -1) { + Process.close_pid(this.pid); // hopefully kills it.. + this.pid = -1; + } + try { + if (this.in_ch != null) this.in_ch.shutdown(true); + if (this.out_ch != null) this.out_ch.shutdown(true); + if (this.err_ch != null) this.err_ch.shutdown(true); + } catch (Error e) { + // error shutting down + } + // blank out channels + this.in_ch = null; + this.err_ch = null; + this.out_ch = null; + // rmeove listeners !! important otherwise we kill the CPU + //if (this.err_src > -1 ) GLib.source_remove(this.err_src); + //if (this.out_src > -1 ) GLib.source_remove(this.out_src); + this.err_src = -1; + this.out_src = -1; + //this.unref(); } @@ -343,64 +413,94 @@ public class Spawn : Object * @arg str {String} string to write to stdin of process * @returns GLib.IOStatus (0 == error, 1= NORMAL) */ - integer void write(str) // write a line to + private int write(string str) throws Error // write a line to { - if (this.in_ch is null) { + if (this.in_ch == null) { return 0; // input is closed } - //print("write: " + str); - // NEEDS GIR FIX! for return value.. let's ignore for the time being.. - //var ret = {}; - //var res = this.in_ch.write_chars(str, str.length, ret); - var res = this.in_ch.write_chars(str, str.length); - - //print("write_char retunred:" + JSON.stringify(res) + ' ' +JSON.stringify(ret) ); - + //print("write: " + str); + // NEEDS GIR FIX! for return value.. let's ignore for the time being.. + //var ret = {}; + size_t written; + var res = this.in_ch.write_chars(str.to_utf8(), out written); + + //print("write_char retunred:" + JSON.stringify(res) + ' ' +JSON.stringify(ret) ); + if (res != GLib.IOStatus.NORMAL) { - throw "Write failed"; + throw new SpawnError.WRITE_ERROR("Write failed"); } //return ret.value; return str.length; - }, + } + /** * read from pipe and call appropriate listerner and add to output or stderr string. * @arg giochannel to read from. * @returns none */ - read: function(ch) + private bool read(IOChannel ch) { - var prop = ch == this.out_ch ? 'output' : 'stderr'; + string prop = (ch == this.out_ch) ? "output" : "stderr"; // print("prop: " + prop); - var _this = this; + //print ("spawn.read: %s\n", prop); - //print(JSON.stringify(ch, null,4)); while (true) { - - var x = {}; - var status = ch.read_line( x); + string buffer; + size_t term_pos; + size_t len; + IOStatus status; + + if (this.pid < 0) { + return false; // spawn complete + closed... can't read any more. + } + + try { + var cond = ch.get_buffer_condition(); + //if ((cond & GLib.IOCondition.ERR) > 0) { + // return false; + //} + //if ((cond & GLib.IOCondition.IN) < 1) { + // return false; + //} + status = ch.read_line( out buffer, out len, out term_pos ); + } catch (Error e) { + //FIXme + return false; + + } + if (buffer == null) { + return false; + } + //print("got buffer of %s\n", buffer); // print('status: ' +JSON.stringify(status)); // print(JSON.stringify(x)); switch(status) { case GLib.IOStatus.NORMAL: //write(fn, x.str); - if (this.listeners[prop]) { - this.listeners[prop].call(this, x.str_return); - } - _this[prop] += x.str_return; - if (_this.debug) { - print(prop + ':' + x.str_return.replace(/\n/, '')); + + //if (this.listeners[prop]) { + // this.listeners[prop].call(this, x.str_return); + //} + if (ch == this.out_ch) { + this.output += buffer; + if (this.cfg.output != null) { + this.cfg.output( buffer); + } + } else { + this.stderr += buffer; } - if (this.async) { - try { - if (imports.gi.Gtk.events_pending()) { - imports.gi.Gtk.main_iteration(); - } - } catch(e) { - + //_this[prop] += x.str_return; + //if (this.cfg.debug) { + // stdout.printf("%s : %s", prop , buffer); + //} + if (this.cfg.async) { + + if ( Gtk.events_pending()) { + Gtk.main_iteration(); } } @@ -408,12 +508,12 @@ public class Spawn : Object continue; case GLib.IOStatus.AGAIN: //print("Should be called again.. waiting for more data.."); - return true; - break; + return true; + //break; case GLib.IOStatus.ERROR: case GLib.IOStatus.EOF: - return false; - break; + return false; + //break; } break; @@ -423,22 +523,8 @@ public class Spawn : Object return false; // allow it to be called again.. } -}; -/** - * @function run - * - * simple run a process - returns result, or throws stderr result... - * @param cfg {Object} see spawn - * @return {string} stdout output. - */ -function run(cfg) { - cfg.exceptions = true; - cfg.async = false; - var s = new Spawn(cfg); - var ret = s.run(); - return s.output; } - /* + /* // test try { Seed.print(run({