1 module app;
2 
3 import core.sync.mutex;
4 import core.exception;
5 
6 import painlessjson;
7 import standardpaths;
8 
9 import workspaced.api;
10 
11 import std.exception;
12 import std.bitmanip;
13 import std.process;
14 import std.traits;
15 import std.stdio : stderr, File;
16 
17 static import std.stdio;
18 import std.json;
19 import std.meta;
20 import std.conv;
21 
22 import source.workspaced.info;
23 
24 __gshared File stdin, stdout;
25 shared static this()
26 {
27 	stdin = std.stdio.stdin;
28 	stdout = std.stdio.stdout;
29 	version (Windows)
30 		std.stdio.stdin = File("NUL", "r");
31 	else version (Posix)
32 		std.stdio.stdin = File("/dev/null", "r");
33 	else
34 		stderr.writeln("warning: no /dev/null implementation on this OS");
35 	std.stdio.stdout = stderr;
36 }
37 
38 static import workspaced.com.dcd;
39 
40 static import workspaced.com.dfmt;
41 
42 static import workspaced.com.dlangui;
43 
44 static import workspaced.com.dscanner;
45 
46 static import workspaced.com.dub;
47 
48 static import workspaced.com.fsworkspace;
49 
50 static import workspaced.com.importer;
51 
52 __gshared Mutex writeMutex, commandMutex;
53 
54 void sendFinal(int id, JSONValue value)
55 {
56 	synchronized (writeMutex)
57 	{
58 		ubyte[] data = nativeToBigEndian(id) ~ (cast(ubyte[]) value.toString());
59 		stdout.rawWrite(nativeToBigEndian(cast(int) data.length) ~ data);
60 		stdout.flush();
61 	}
62 }
63 
64 void broadcast(JSONValue value)
65 {
66 	sendFinal(0x7F000000, value);
67 }
68 
69 void send(int id, JSONValue[] values)
70 {
71 	if (values.length == 0)
72 	{
73 		throw new Exception("Unknown arguments!");
74 	}
75 	else if (values.length == 1)
76 	{
77 		sendFinal(id, values[0]);
78 	}
79 	else
80 	{
81 		sendFinal(id, JSONValue(values));
82 	}
83 }
84 
85 JSONValue toJSONArray(T)(T value)
86 {
87 	JSONValue[] vals;
88 	foreach (val; value)
89 	{
90 		vals ~= JSONValue(val);
91 	}
92 	return JSONValue(vals);
93 }
94 
95 alias Identity(I...) = I;
96 
97 template JSONCallBody(alias T, string fn, string jsonvar, size_t i, Args...)
98 {
99 	static if (Args.length == 1 && Args[0] == "request" && is(Parameters!T[0] == JSONValue))
100 		enum JSONCallBody = jsonvar;
101 	else static if (Args.length == i)
102 		enum JSONCallBody = "";
103 	else static if (is(ParameterDefaults!T[i] == void))
104 		enum JSONCallBody = "(fromJSON!(Parameters!(" ~ fn ~ ")[" ~ i.to!string
105 				~ "])(*enforce(`" ~ Args[i] ~ "` in " ~ jsonvar ~ ", `"
106 				~ Args[i] ~ " has no default value and is not in the JSON request`))),"
107 				~ JSONCallBody!(T, fn, jsonvar, i + 1, Args);
108 	else
109 					enum JSONCallBody = "(`" ~ Args[i] ~ "` in " ~ jsonvar ~ ") ? fromJSON!(Parameters!("
110 							~ fn ~ ")[" ~ i.to!string ~ "])(" ~ jsonvar ~ "[`" ~ Args[i] ~ "`]"
111 							~ ") : ParameterDefaults!(" ~ fn ~ ")[" ~ i.to!string ~ "]," ~ JSONCallBody!(T,
112 									fn, jsonvar, i + 1, Args);
113 }
114 
115 template JSONCallNoRet(alias T, string fn, string jsonvar, bool async)
116 {
117 	alias Args = ParameterIdentifierTuple!T;
118 	static if (Args.length > 0)
119 		enum JSONCallNoRet = fn ~ "(" ~ (async ? "asyncCallback,"
120 					: "") ~ JSONCallBody!(T, fn, jsonvar, async ? 1 : 0, Args) ~ ")";
121 	else
122 		enum JSONCallNoRet = fn ~ "(" ~ (async ? "asyncCallback" : "") ~ ")";
123 }
124 
125 template JSONCall(alias T, string fn, string jsonvar, bool async)
126 {
127 	static if (async)
128 	{
129 		static assert(is(ReturnType!T == void),
130 				"Async functions cant have an return type! For function " ~ fn);
131 		enum JSONCall = JSONCallNoRet!(T, fn, jsonvar, async) ~ ";";
132 	}
133 	else
134 	{
135 		alias Ret = ReturnType!T;
136 		static if (is(Ret == void))
137 			enum JSONCall = JSONCallNoRet!(T, fn, jsonvar, async) ~ ";";
138 		else
139 			enum JSONCall = "values ~= " ~ JSONCallNoRet!(T, fn, jsonvar, async) ~ ".toJSON;";
140 	}
141 }
142 
143 template compatibleGetUDAs(alias symbol, alias attribute)
144 {
145 	import std.typetuple : Filter;
146 
147 	template isDesiredUDA(alias S)
148 	{
149 		static if (__traits(compiles, is(typeof(S) == attribute)))
150 		{
151 			enum isDesiredUDA = is(typeof(S) == attribute);
152 		}
153 		else
154 		{
155 			enum isDesiredUDA = isInstanceOf!(attribute, typeof(S));
156 		}
157 	}
158 
159 	alias compatibleGetUDAs = Filter!(isDesiredUDA, __traits(getAttributes, symbol));
160 }
161 
162 void handleRequestMod(alias T)(int id, JSONValue request, ref JSONValue[] values,
163 		ref int asyncWaiting, ref bool isAsync, ref bool hasArgs, in AsyncCallback asyncCallback)
164 {
165 	foreach (name; __traits(derivedMembers, T))
166 	{
167 		static if (__traits(compiles, __traits(getMember, T, name)))
168 		{
169 			alias symbol = Identity!(__traits(getMember, T, name));
170 			static if (isSomeFunction!symbol && __traits(getProtection, symbol[0]) == "public")
171 			{
172 				bool matches = false;
173 				foreach (Arguments args; compatibleGetUDAs!(symbol, Arguments))
174 				{
175 					if (!matches)
176 					{
177 						foreach (arg; args.arguments)
178 						{
179 							if (!matches)
180 							{
181 								auto nodeptr = arg.key in request;
182 								if (nodeptr && *nodeptr == arg.value)
183 									matches = true;
184 							}
185 						}
186 					}
187 				}
188 				static if (hasUDA!(symbol, any))
189 					matches = true;
190 				static if (hasUDA!(symbol, component))
191 				{
192 					if (("cmd" in request) !is null && request["cmd"].type == JSON_TYPE.STRING
193 							&& compatibleGetUDAs!(symbol, component)[0].name != request["cmd"].str)
194 						matches = false;
195 				}
196 				static if (hasUDA!(symbol, load) && hasUDA!(symbol, component))
197 				{
198 					if (("components" in request) !is null && ("cmd" in request) !is null
199 							&& request["cmd"].type == JSON_TYPE.STRING && request["cmd"].str == "load")
200 					{
201 						if (request["components"].type == JSON_TYPE.ARRAY)
202 						{
203 							foreach (com; request["components"].array)
204 								if (com.type == JSON_TYPE.STRING
205 										&& com.str == compatibleGetUDAs!(symbol, component)[0].name)
206 									matches = true;
207 						}
208 						else if (request["components"].type == JSON_TYPE.STRING
209 								&& request["components"].str == compatibleGetUDAs!(symbol, component)[0].name)
210 							matches = true;
211 					}
212 				}
213 				static if (hasUDA!(symbol, unload) && hasUDA!(symbol, component))
214 				{
215 					if (("components" in request) !is null && ("cmd" in request) !is null
216 							&& request["cmd"].type == JSON_TYPE.STRING && request["cmd"].str == "unload")
217 					{
218 						if (request["components"].type == JSON_TYPE.ARRAY)
219 						{
220 							foreach (com; request["components"].array)
221 								if (com.type == JSON_TYPE.STRING && (com.str == compatibleGetUDAs!(symbol,
222 										component)[0].name || com.str == "*"))
223 									matches = true;
224 						}
225 						else if (request["components"].type == JSON_TYPE.STRING
226 								&& (request["components"].str == compatibleGetUDAs!(symbol,
227 									component)[0].name || request["components"].str == "*"))
228 							matches = true;
229 					}
230 				}
231 				if (matches)
232 				{
233 					static if (hasUDA!(symbol, async))
234 					{
235 						assert(!hasArgs);
236 						isAsync = true;
237 						asyncWaiting++;
238 						mixin(JSONCall!(symbol[0], "symbol[0]", "request", true));
239 					}
240 					else
241 					{
242 						assert(!isAsync);
243 						hasArgs = true;
244 						mixin(JSONCall!(symbol[0], "symbol[0]", "request", false));
245 					}
246 				}
247 			}
248 		}
249 	}
250 }
251 
252 void handleRequest(int id, JSONValue request)
253 {
254 	if (("cmd" in request) && request["cmd"].type == JSON_TYPE.STRING
255 			&& request["cmd"].str == "version")
256 	{
257 		sendFinal(id, getVersionInfoJson);
258 		return;
259 	}
260 
261 	JSONValue[] values;
262 	JSONValue[] asyncValues;
263 	int asyncWaiting = 0;
264 	bool isAsync = false;
265 	bool hasArgs = false;
266 
267 	const AsyncCallback asyncCallback = (err, value) {
268 		synchronized (commandMutex)
269 		{
270 			try
271 			{
272 				assert(isAsync);
273 				if (err)
274 					throw err;
275 				asyncValues ~= value;
276 				asyncWaiting--;
277 				if (asyncWaiting <= 0)
278 					send(id, asyncValues);
279 			}
280 			catch (Exception e)
281 			{
282 				processException(id, e);
283 			}
284 			catch (AssertError e)
285 			{
286 				processException(id, e);
287 			}
288 		}
289 	};
290 
291 	handleRequestMod!(workspaced.com.dub)(id, request, values, asyncWaiting,
292 			isAsync, hasArgs, asyncCallback);
293 	handleRequestMod!(workspaced.com.dcd)(id, request, values, asyncWaiting,
294 			isAsync, hasArgs, asyncCallback);
295 	handleRequestMod!(workspaced.com.dfmt)(id, request, values, asyncWaiting,
296 			isAsync, hasArgs, asyncCallback);
297 	handleRequestMod!(workspaced.com.dscanner)(id, request, values, asyncWaiting,
298 			isAsync, hasArgs, asyncCallback);
299 	handleRequestMod!(workspaced.com.dlangui)(id, request, values, asyncWaiting,
300 			isAsync, hasArgs, asyncCallback);
301 	handleRequestMod!(workspaced.com.fsworkspace)(id, request, values,
302 			asyncWaiting, isAsync, hasArgs, asyncCallback);
303 	handleRequestMod!(workspaced.com.importer)(id, request, values, asyncWaiting,
304 			isAsync, hasArgs, asyncCallback);
305 
306 	if (isAsync)
307 	{
308 		if (values.length > 0)
309 			throw new Exception("Cannot mix sync and async functions! In request " ~ request.toString);
310 	}
311 	else
312 	{
313 		if (hasArgs && values.length == 0)
314 			sendFinal(id, JSONValue(null));
315 		else
316 			send(id, values);
317 	}
318 }
319 
320 void processException(int id, Throwable e)
321 {
322 	stderr.writeln(e);
323 	// dfmt off
324 	sendFinal(id, JSONValue([
325 		"error": JSONValue(true),
326 		"msg": JSONValue(e.msg),
327 		"exception": JSONValue(e.toString())
328 	]));
329 	// dfmt on
330 }
331 
332 void processException(int id, JSONValue request, Throwable e)
333 {
334 	stderr.writeln(e);
335 	// dfmt off
336 	sendFinal(id, JSONValue([
337 		"error": JSONValue(true),
338 		"msg": JSONValue(e.msg),
339 		"exception": JSONValue(e.toString()),
340 		"request": request
341 	]));
342 	// dfmt on
343 }
344 
345 int main(string[] args)
346 {
347 	import std.file;
348 	import etc.linux.memoryerror;
349 
350 	version (unittest)
351 	{
352 	}
353 	else
354 	{
355 		static if (is(typeof(registerMemoryErrorHandler)))
356 			registerMemoryErrorHandler();
357 
358 		if (args.length > 1 && (args[1] == "-v" || args[1] == "--version" || args[1] == "-version"))
359 		{
360 			stdout.writeln(getVersionInfoString);
361 			return 0;
362 		}
363 
364 		broadcastCallback = &broadcast;
365 
366 		writeMutex = new Mutex;
367 		commandMutex = new Mutex;
368 
369 		int length = 0;
370 		int id = 0;
371 		ubyte[4] intBuffer;
372 		ubyte[] dataBuffer;
373 		JSONValue data;
374 
375 		scope (exit)
376 			handleRequest(int.min, JSONValue(["cmd" : "unload", "components" : "*"]));
377 
378 		stderr.writeln("Config files stored in ", standardPaths(StandardPath.config, "workspace-d"));
379 
380 		while (stdin.isOpen && stdout.isOpen && !stdin.eof)
381 		{
382 			dataBuffer = stdin.rawRead(intBuffer);
383 			assert(dataBuffer.length == 4, "Unexpected buffer data");
384 			length = bigEndianToNative!int(dataBuffer[0 .. 4]);
385 
386 			assert(length >= 4, "Invalid request");
387 
388 			dataBuffer = stdin.rawRead(intBuffer);
389 			assert(dataBuffer.length == 4, "Unexpected buffer data");
390 			id = bigEndianToNative!int(dataBuffer[0 .. 4]);
391 
392 			dataBuffer.length = length - 4;
393 			dataBuffer = stdin.rawRead(dataBuffer);
394 
395 			try
396 			{
397 				data = parseJSON(cast(string) dataBuffer);
398 			}
399 			catch (Exception e)
400 			{
401 				processException(id, e);
402 			}
403 			catch (AssertError e)
404 			{
405 				processException(id, e);
406 			}
407 
408 			try
409 			{
410 				handleRequest(id, data);
411 			}
412 			catch (Exception e)
413 			{
414 				processException(id, data, e);
415 			}
416 			catch (AssertError e)
417 			{
418 				processException(id, data, e);
419 			}
420 			stdout.flush();
421 		}
422 	}
423 	return 0;
424 }