summaryrefslogtreecommitdiff
path: root/src/tmate/session.vala
blob: 5571654dc4da9e9186e79d066d16e3a0468105be (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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<string>();
				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<string>();
				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;
			}
	}
}