namespace Tmate { public interface SessionInterface : Object { public abstract string? @get(SessionType session); public abstract bool start(string? config = null); public abstract bool stop(); } [SingleInstance] public class Session: Object, SessionInterface { private string? _addr_ssh = null; private string? _addr_http = null; private string? _addr_ssh_ro = null; private string? _addr_http_ro = null; private IOChannel? stdout = null; private Pid? pid = null; // Regex private Regex r_connect = /^Connecting to .+\.\.\.$/; private Regex r_socket = /^To connect.+ tmate -S (.+) attach$/; private Regex r_http = /^web \w+[:] (.+)$/; private Regex r_ssh = /^ssh \w+[:] .+ (.+)+$/; private Regex r_http_ro = /^web \w+ read \w+[:] (.+)$/; private Regex r_ssh_ro = /^ssh \w+ read \w+[:] .+ (.+)+$/; private Regex r_joined = /^.+ joined \(([^()]+)\) -- (\d+) clients? .+$/; private Regex r_left = /^.+ left \(([^()]+)\) -- (\d+) clients? .+$/; public SessionType flags { get; private set;} public string? socket { get; private set;} construct { reset(); } public virtual signal void started (string socket) { info (_("Session: unix://%s"), socket); } public virtual signal void stopped () { info (_("Session terminated.")); } public override signal void address (SessionType session, string address) { info (_("%s address: %s"), session.to_string(), address); switch(session) { case SSH : _addr_ssh = address; break; case HTTP: _addr_http = address; break; case HTTP_READONLY: _addr_http_ro = address; break; case SSH_READONLY : _addr_ssh_ro = address; break; case DISCONNECTED : _addr_http = _addr_ssh = _addr_http_ro = _addr_ssh_ro = null; break; default: warn_if_reached(); break; } } public virtual signal void joined (string mate, int mates) { info(_("%s joined, connected: %d"), mate, mates); } public virtual signal void left (string mate, int mates) { info(_("%s left, connected: %d"), mate, mates); } private void reset() { _addr_ssh = null; _addr_http = null; _addr_ssh_ro = null; _addr_http_ro = null; stdout = null; pid = null; socket = null; flags = DISCONNECTED; } private bool send(string[] args) { if(socket == null) return false; var command = new GenericArray(); command.data = { "/usr/bin/tmate", "-S", socket }; foreach(var arg in args) command.add(arg); try { return Process.spawn_sync(null, command.data, null, STDERR_TO_DEV_NULL|STDOUT_TO_DEV_NULL, null, null, null, null); } catch (SpawnError e) { return false; } } public new string? @get(SessionType session) { switch(session) { case SSH: return _addr_ssh; case HTTP: return _addr_http; case SSH_READONLY : return _addr_ssh_ro; case HTTP_READONLY: return _addr_http_ro; case DISCONNECTED : warning("No running session."); break; default: warning("Unknown Session Type %s", session.to_string()); break; } return null; } public bool start(string? config = null) { if(! (pid == null)) { started.emit(socket); return true; } int out_fd; var args = new GenericArray(); args.data = {"/usr/bin/tmate", "-F"}; if(! (config == null) && FileUtils.test(config, IS_REGULAR)) { args.add("-f"); args.add(config); } try { Process.spawn_async_with_pipes( null, args.data, null, STDERR_TO_DEV_NULL | DO_NOT_REAP_CHILD, null, out pid, null, out out_fd, null ); stdout = new IOChannel.unix_new(out_fd); stdout.add_watch(IOCondition.IN | IOCondition.HUP, (channel, condition) => { if (condition == IOCondition.HUP) return false; try { string line; MatchInfo info; channel.read_line (out line, null, null); if(r_socket.match(line, 0, out info)) socket = info.fetch(1); else if (r_connect.match(line) && socket != null) started.emit(socket); else if(r_http.match(line, 0, out info)) address.emit(HTTP, info.fetch(1)); else if(r_http_ro.match(line, 0, out info)) address.emit(HTTP_READONLY, info.fetch(1)); else if(r_ssh.match(line, 0, out info)) address.emit(SSH, info.fetch(1)); else if(r_ssh_ro.match(line, 0, out info)) address.emit(SSH_READONLY, info.fetch(1)); else debug("Unprocessed line: \"%s\"", line); } catch (IOChannelError e) { print ("IOChannelError: %s\n", e.message); return false; } catch (ConvertError e) { print ("ConvertError: %s\n", e.message); return false; } return true; }); ChildWatch.add(pid, (pid, status) => { Process.close_pid(pid); reset(); stopped.emit(); }); return true; } catch (SpawnError e) { warning("Error: %s", e.message); reset(); return false; } } public bool stop() { return false; } } }