summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Mubashshir <ahm@jadupc.com>2023-10-08 18:07:04 +0600
committerLibravatar Mubashshir <ahm@jadupc.com>2023-10-08 18:07:04 +0600
commit6aaf6e6766906cf37a87bffc171b92b74df8095e (patch)
tree3f412b0b83c7af33c7feaf436e7a643bcb076730
parent735d125f79fc8bb8234bb255ccd8573d010c13a1 (diff)
downloadjadupc-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.vala39
-rw-r--r--src/tmate/session.vala252
-rw-r--r--src/tmate/sessiontype.vala83
-rw-r--r--src/tmate/stdout.vala69
-rw-r--r--src/window.vala20
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 => {