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 }