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 }