diff options
author | 2023-10-08 18:07:04 +0600 | |
---|---|---|
committer | 2023-10-08 18:07:04 +0600 | |
commit | 6aaf6e6766906cf37a87bffc171b92b74df8095e (patch) | |
tree | 3f412b0b83c7af33c7feaf436e7a643bcb076730 | |
parent | 735d125f79fc8bb8234bb255ccd8573d010c13a1 (diff) | |
download | jadupc-remote-support-console-6aaf6e6766906cf37a87bffc171b92b74df8095e.tar.gz jadupc-remote-support-console-6aaf6e6766906cf37a87bffc171b92b74df8095e.zip |
API: Use Stdout Parser to generate events
Signed-off-by: Mubashshir <ahm@jadupc.com>
-rw-r--r-- | src/tmate/config.vala | 39 | ||||
-rw-r--r-- | src/tmate/session.vala | 252 | ||||
-rw-r--r-- | src/tmate/sessiontype.vala | 83 | ||||
-rw-r--r-- | src/tmate/stdout.vala | 69 | ||||
-rw-r--r-- | src/window.vala | 20 |
5 files changed, 249 insertions, 214 deletions
diff --git a/src/tmate/config.vala b/src/tmate/config.vala index 8957d02..9f70191 100644 --- a/src/tmate/config.vala +++ b/src/tmate/config.vala @@ -1,9 +1,10 @@ namespace Tmate { - [SingleInstance] - class Config : Object + public class Config : Object { private static HashTable<string, string> cfg; + private File? backing_file = null; + construct { cfg = new HashTable<string, string>(str_hash, str_equal); cfg.insert("server-host", "tty.dev.jadupc.com"); @@ -12,27 +13,32 @@ namespace Tmate cfg.insert("server-ed25519-fingerprint", "SHA256:w/TRuOK0w5qDXNBKdlYlANgZwq3Xg5LSZlBYIwEH8gU"); } - public void @delete(string? path) + ~Config() + { + this.delete(); + } + + public void @delete() { - if (path == null) return; - var fcfg = File.new_for_path(path); + if (backing_file == null) return; try { - fcfg.delete(); + backing_file.delete(); + backing_file = null; } catch (Error e) { print(@"Error: $(e.message)\n"); } } - public new string? @get() + public new string? get_path() { - bool failed = false; - string cfgpath = ".."; + if (null != backing_file) + return backing_file.get_path(); + bool failed = false; try { FileIOStream scfg; - var fcfg = File.new_tmp(".tmate.conf.XXXXXXXXX", out scfg); - var cfgout = new DataOutputStream(scfg.output_stream); - cfgpath = fcfg.get_path(); + backing_file = File.new_tmp(".tmate.conf.XXXXXXXXX", out scfg); + var cfgout = new DataOutputStream(scfg.output_stream); cfg.foreach((key, val) => { try { @@ -46,8 +52,15 @@ namespace Tmate } catch (Error e) { print(@"Error: $(e.message)\n"); failed = true; + if(null != backing_file) + try { + backing_file.delete(); + } catch(Error e) { + print(@"Error: $(e.message)\n"); + } + backing_file = null; } - return failed ? null : cfgpath; + return failed ? null : backing_file.get_path(); } } } diff --git a/src/tmate/session.vala b/src/tmate/session.vala index 356aa66..fae55b6 100644 --- a/src/tmate/session.vala +++ b/src/tmate/session.vala @@ -2,122 +2,81 @@ namespace Tmate { public interface SessionInterface : Object { - public abstract signal void address (SessionType session, string address); - - public abstract string? @get(SessionType session); - public abstract bool start(string? config = null); + public abstract signal void started (string socket); + public abstract signal void stopped (); + public abstract signal void address_changed (SessionType session, string address); + public abstract signal void error (string summery, string message = ""); + public abstract signal void joined (string mate, int mates); + public abstract signal void left (string mate, int mates); + + public abstract bool start(Config? 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; - private uint error_count = 0; - - // 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? .+$/; - private Regex r_closed = /^Session closed$/; - private Regex r_note = /^(Note: |Reconnecting)/; - private Regex r_restarted = /^Session shell restarted$/; - private Regex r_netfail= /^[^ ]+ lookup failure\..+ \((.+)\)$/; - - public SessionType flags { get; private set;} - public string? socket { get; private set;} - public bool terminate = true; - - construct { reset(); } - - public virtual signal void started (string socket) - { - info (_("Session: unix://%s"), socket); - } + private IOChannel? stdout = null; + private Pid? pid = null; + private uint error_count = 0; - public virtual signal void network_error(string message) - { - if(error_count ++ > 5) stop(); - } + public bool terminate = true; + public SessionAddress address { get; construct; } + public string? socket { get; private set;} + public Config? config { get; private set;} - public virtual signal void stopped () - { - info (_("Session terminated.")); + construct { + address = new SessionAddress(); reset(); + address.clear(); + error.connect_after(() => { + if(error_count++ < 5) return; + + stop(); + }); } - private void address_set(SessionType session, string address) + private void address_set(SessionType session, string? address) { - info (_("%s address: %s"), session.to_string(), address); - - if (session in SessionType.SSH|SessionType.SSH_READONLY|SessionType.HTTP|SessionType.HTTP_READONLY) { - 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; - default: - warn_if_reached(); - break; - } - - flags |= session; - this.address(session, address); - } else if (session == DISCONNECTED) { - _addr_http = _addr_ssh = _addr_http_ro = _addr_ssh_ro = null; - flags = DISCONNECTED; - this.address(DISCONNECTED, ""); + if (SessionType.SSH in session || SessionType.HTTP in session) { + debug("CALL: %s\t%s", session.to_string(), address); + if(address != null || address != "") + this.address.add(session, address); + + if(this.address[session] != null) + address_changed (session, this.address[session]); } } - 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) + + private void reset() { - info(_("%s left, connected: %d"), mate, mates); + stdout = null; + pid = null; + socket = null; + error_count = 0; + address.clear(); + delete_config(); } - private void reset() + internal void delete_config() { - _addr_ssh = null; - _addr_http = null; - _addr_ssh_ro = null; - _addr_http_ro = null; - stdout = null; - pid = null; - socket = null; - flags = DISCONNECTED; - error_count = 0; + if(null == config) return; + + config.delete(); + config = null; } private bool send(string[] args) { - if(socket == null) + if(pid == null) return false; + var command = new GenericArray<string>(); command.data = { "/usr/bin/tmate", "-S", socket }; + foreach(var arg in args) command.add(arg); @@ -131,50 +90,38 @@ namespace Tmate } - public new string? @get(SessionType session) + private void restart() { - 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; + if(terminate) { + stop(); + return; } - return null; + started(socket); + address_set(SSH, null); + address_set(HTTP, null); + address_set(SSH | READONLY, null); + address_set(HTTP | READONLY, null); } public bool stop() { - return send({"kill-server"}); - } - - private void restart() - { - if(terminate) { - stop(); - return; + debug("stopping!"); + var ret = send({"kill-server"}); + if (! send({"has"})) { + debug("stopped!"); + Idle.add(() => { + stopped(); + return false; + }); } - started(socket); - if((bool)_addr_ssh) address(SSH, _addr_ssh); - if((bool)_addr_ssh_ro) address(SSH_READONLY, _addr_ssh_ro); - if((bool)_addr_http) address(HTTP, _addr_http); - if((bool)_addr_http_ro) address(HTTP_READONLY, _addr_http_ro); + return ret; } - public bool start(string? config = null) + public bool start(Config? config = null) { - if(! (pid == null)) { + if(null != pid) { started(socket); return true; } @@ -183,10 +130,12 @@ namespace Tmate var args = new GenericArray<string>(); args.data = {"/usr/bin/tmate", "-F"}; - if(! (config == null) && FileUtils.test(config, IS_REGULAR)) { + if(null != config) { + this.config = config; args.add("-f"); - args.add(config); + args.add(config.get_path()); } + try { Process.spawn_async_with_pipes( null, args.data, null, @@ -202,29 +151,42 @@ namespace Tmate 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(socket); - else if(r_http.match(line, 0, out info)) - address_set(HTTP, info.fetch(1)); - else if(r_http_ro.match(line, 0, out info)) - address_set(HTTP_READONLY, info.fetch(1)); - else if(r_ssh.match(line, 0, out info)) - address_set(SSH, info.fetch(1)); - else if(r_ssh_ro.match(line, 0, out info)) - address_set(SSH_READONLY, info.fetch(1)); - else if(r_restarted.match(line)) + Stdout.Token token = (new Stdout()).parse(line); + debug(token.to_string()); + + switch(token.class) { + case SOCKET: + socket = token.list[0]; + return true; + case INIT: + started (socket); + delete_config(); + return true; + case ADDRESS: + address_set( + SessionType.from_string( + token.list[0], token.list[1] + ), token.list[2]); + return true; + case RESTART: restart(); - else if(r_closed.match(line)) + return true; + case TERM: + info (_("Session terminated.")); stopped(); - else if(r_netfail.match(line, 0, out info)) - network_error(info.fetch(1)); - else if(r_note.match(line)) unlikely(false); // Ignore line - else - debug("Unprocessed line: \"%s\"", line); + return false; + case ERROR: + this.error(token.list[0], token.list[1]); + return true; + case COMMENT: + return true; // not needed. + case MATES: + return true;// TODO: handle mates join/leave + case UNKNOWN: + debug("%s", token.list[0]); + return true; + } } catch (IOChannelError e) { print ("IOChannelError: %s\n", e.message); return false; @@ -237,13 +199,15 @@ namespace Tmate ChildWatch.add(pid, (pid, status) => { Process.close_pid(pid); - if(flags != DISCONNECTED) - stopped(); + reset(); + stopped(); }); return true; } catch (SpawnError e) { warning("Error: %s", e.message); + this.error(e.message); + reset(); stopped(); return false; } diff --git a/src/tmate/sessiontype.vala b/src/tmate/sessiontype.vala index 4c44aea..f0da496 100644 --- a/src/tmate/sessiontype.vala +++ b/src/tmate/sessiontype.vala @@ -2,27 +2,92 @@ namespace Tmate { [Flags] public enum SessionType { - DISCONNECTED, - SSH, HTTP, - SSH_READONLY, - HTTP_READONLY; + SSH = 1, HTTP = 2, + READONLY = 4; public string to_string() { switch(this) { - case DISCONNECTED : - return "Disconnected"; case SSH : return "SessionType.SSH"; case HTTP : return "SessionType.HTTP"; - case SSH_READONLY : + case SSH | READONLY: return "SessionType.SSH:ReadOnly"; - case HTTP_READONLY: - return "SessionType.HTTP.ReadOnly"; + case HTTP | READONLY: + return "SessionType.HTTP:ReadOnly"; default : warn_if_reached(); return "SessionType.Invalid"; } } + + public static SessionType from_string(string cat, string mod) + { + SessionType ret = 0; + switch(cat.down()) { + case "ssh": + ret = SSH ; + break; + case "web": + ret = HTTP; + break; + } + if(mod == "read only") + ret |= READONLY; + return ret; + } + } + + public class SessionAddress + { + public string? ssh { get; internal set; } + public string? http { get; internal set; } + public string? ssh_ro { get; internal set; } + public string? http_ro { get; internal set; } + + internal unowned string? add(SessionType flag, string? data) + { + debug("SET: %s\t%s", flag.to_string(), data); + + switch(flag) { + case SessionType.SSH: + this.ssh = data; + return this.ssh; + case SessionType.HTTP: + this.http = data; + return this.http; + case SessionType.SSH | SessionType.READONLY: + this.ssh_ro = data; + return this.ssh_ro; + case SessionType.HTTP| SessionType.READONLY: + this.http_ro = data; + return this.http_ro; + default: + warn_if_reached(); + return null; + } + } + + public string @get(SessionType flag) + { + switch(flag) { + case SessionType.SSH: + return this.ssh; + case SessionType.HTTP: + return this.http; + case SessionType.SSH | SessionType.READONLY: + return this.ssh_ro; + case SessionType.HTTP | SessionType.READONLY: + return this.http_ro; + default: + warn_if_reached(); + return ""; + } + } + + internal void clear() + { + this.ssh = this.ssh_ro = this.http = this.http_ro = null; + } } } diff --git a/src/tmate/stdout.vala b/src/tmate/stdout.vala index 804ccbb..223eb26 100644 --- a/src/tmate/stdout.vala +++ b/src/tmate/stdout.vala @@ -5,9 +5,9 @@ namespace Tmate class Stdout : Object { public enum TokenType { - SOCKET, ADDRESS, MATES, EMPTY, - COMMENT, RESTART, INIT, TERM, - UNRECOGNIZED, NET_ERROR; + SOCKET, ADDRESS, MATES, + EMPTY, COMMENT, RESTART, + INIT, TERM, UNKNOWN, ERROR; public string to_string() { return EnumClass.to_string(typeof(TokenType), this) @@ -18,29 +18,24 @@ namespace Tmate public class Token { public TokenType @class {get; private set;} - public string[] tokens {get; private set;} - public Token(string[] tokens, TokenType @class = UNRECOGNIZED) + public string[] list {get; private set;} + public Token(string[] list, TokenType @class = UNKNOWN) { this.class = @class; - this.tokens = tokens; + this.list = list; } public string to_string() { - var sep = ""; - var end = ""; - - if (this.tokens.length > 1) { - sep += "\n\t"; - end += "\n"; - } - if(this.tokens.length > 0) { - sep += "\""; - end += "\""; - } - return @"<$(this.class) {$sep$( - string.joinv(@"\", $sep", this.tokens) - )$(end.reverse()) - }>"; + if (this.list.length == 0) + return "<%s>".printf(this.@class.to_string()); + else if(this.list.length == 1) + return "<%s {\"%s\"}>".printf( + this.@class.to_string(), + this.list[0]); + else + return "<%s {\n\t\"%s\"\n}>".printf( + this.@class.to_string(), + string.joinv("\",\n\t\"", this.list)); } } @@ -50,36 +45,38 @@ namespace Tmate private Regex r_closed = /^Session closed$/; private Regex r_restarted = /^Session shell restarted$/; private Regex r_comment = /^(Note: |Reconnecting)/; - private Regex r_dnsfail = /^([^ ]+ lookup failure\.).+ \((.+)\)$/; - private Regex r_netfail = /^(Error [^:]+)[:] [^:]+[:] (.+)$/; + private Regex r_dnsfail = /^(?P<body>[^ ]+ lookup failure\.).+ \((non-recoverable )?(?P<summery>.+)\)$/; + private Regex r_netfail = /^(?P<summery>Error [^:]+)[:] [^:]+[:] (?P<body>.+)$/; private Regex r_address = /^(ssh|web) \w+ ?(|read only)[:](?:|.+) ([^ ]+)$/; + private MatchInfo info; - public Token parse(string line) + public Token parse(string input) { - MatchInfo info; + var line = input.strip(); + if(line == "") return new Token({}, EMPTY); else if(r_mates.match(line, 0, out info)) return new Token({ - info.fetch(1), // joined | left - info.fetch(2), // mate ip/domain - info.fetch(3) // active mates - }, MATES); + info.fetch(1), // joined | left + info.fetch(2), // mate ip/domain + info.fetch(3) // active mates + }, MATES); else if(r_dnsfail.match(line, 0, out info) || r_netfail.match(line, 0, out info)) return new Token({ - info.fetch(1), - info.fetch(2), - }, NET_ERROR); + info.fetch_named("summery"), + info.fetch_named("body"), + }, ERROR); else if(r_comment.match(line)) return new Token({line}, COMMENT); else if(r_restarted.match(line)) return new Token({}, RESTART); else if(r_address.match(line, 0, out info)) return new Token({ - info.fetch(1), // web | ssh - info.fetch(2), // read only? - info.fetch(3) // address - }, ADDRESS); + info.fetch(1), // web | ssh + info.fetch(2), // read only? + info.fetch(3) // address + }, ADDRESS); else if(r_socket.match(line, 0, out info)) return new Token({ info.fetch(1) }, SOCKET); else if(r_closed.match(line)) diff --git a/src/window.vala b/src/window.vala index 640e287..8a9eeaa 100644 --- a/src/window.vala +++ b/src/window.vala @@ -32,9 +32,7 @@ namespace JadupcSupport [GtkChild] private unowned Gtk.Label infobarText; [GtkChild] private unowned Gtk.Image infobarIcon; - private Tmate.Config configFactory; private Tmate.Session tmate; - private ulong? session_handler = null; public Window (Gtk.Application app) { @@ -42,17 +40,21 @@ namespace JadupcSupport utils.realize_all(this); // Realize all children to pacify Gtk. icon_name = "com.jadupc.support"; - configFactory = new Tmate.Config(); tmate = new Tmate.Session(); infobar.response.connect(() => infobar.set_revealed(false)); + delete_event.connect(() => { + tmate.delete_config(); + tmate.stop(); + return false; + }); - tmate.network_error.connect((message) => { + tmate.error.connect((message) => { show_in_infobar(ERROR, message); }); tmate.stopped.connect(reset_window); - tmate.address.connect((stype, addr) => { + tmate.address_changed.connect((stype, addr) => { if(Tmate.SessionType.SSH in stype) on_session_connect(addr); }); @@ -63,13 +65,7 @@ namespace JadupcSupport session.label = _("Connecting..."); view.visible_child_name = "inprogress"; - var config = configFactory.get(); - session_handler = tmate.started.connect(() => { - configFactory.delete(config); - tmate.disconnect(session_handler); - }); - - tmate.start(config); + tmate.start(new Tmate.Config()); }); terminal.child_exited.connect(status => { |