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