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 import workspaced.coms; 11 12 import std.algorithm; 13 import std.bitmanip; 14 import std.exception; 15 import std.functional; 16 import std.process; 17 import std.stdio : File, stderr; 18 import std.traits; 19 20 static import std.stdio; 21 import std.string; 22 import std.json; 23 import std.meta; 24 import std.conv; 25 26 import source.workspaced.info; 27 28 __gshared File stdin, stdout; 29 shared static this() 30 { 31 stdin = std.stdio.stdin; 32 stdout = std.stdio.stdout; 33 version (Windows) 34 std.stdio.stdin = File("NUL", "r"); 35 else version (Posix) 36 std.stdio.stdin = File("/dev/null", "r"); 37 else 38 stderr.writeln("warning: no /dev/null implementation on this OS"); 39 std.stdio.stdout = stderr; 40 } 41 42 __gshared Mutex writeMutex, commandMutex; 43 44 void sendResponse(int id, JSONValue message) 45 { 46 synchronized (writeMutex) 47 { 48 ubyte[] data = nativeToBigEndian(id) ~ (cast(ubyte[]) message.toString()); 49 stdout.rawWrite(nativeToBigEndian(cast(int) data.length) ~ data); 50 stdout.flush(); 51 } 52 } 53 54 void sendException(int id, Throwable t) 55 { 56 JSONValue[string] message; 57 message["error"] = JSONValue(true); 58 message["msg"] = JSONValue(t.msg); 59 message["exception"] = JSONValue(t.toString); 60 sendResponse(id, JSONValue(message)); 61 } 62 63 void broadcast(WorkspaceD workspaced, WorkspaceD.Instance instance, JSONValue message) 64 { 65 sendResponse(0x7F000000, JSONValue(["workspace" : JSONValue(instance 66 ? instance.cwd : null), "data" : message])); 67 } 68 69 void bindFail(WorkspaceD.Instance instance, ComponentFactory component, Exception error) 70 { 71 sendResponse(0x7F000000, JSONValue(["workspace" : JSONValue(instance 72 ? instance.cwd : null), "data" : JSONValue(["component" : JSONValue(component.info.name), 73 "type" : JSONValue("bindfail"), "msg" : JSONValue(error.msg), "trace" 74 : JSONValue(error.toString)])])); 75 } 76 77 WorkspaceD engine; 78 79 void handleRequest(int id, JSONValue request) 80 { 81 if (request.type != JSON_TYPE.OBJECT || "cmd" !in request 82 || request["cmd"].type != JSON_TYPE.STRING) 83 { 84 goto printUsage; 85 } 86 else if (request["cmd"].str == "version") 87 { 88 sendResponse(id, getVersionInfoJson); 89 } 90 else if (request["cmd"].str == "load") 91 { 92 if ("component" !in request || request["component"].type != JSON_TYPE.STRING) 93 { 94 sendException(id, 95 new Exception( 96 `Expected load message to be in format {"cmd":"load", "component":string, ("autoregister":bool)}`)); 97 } 98 else 99 { 100 bool autoRegister = true; 101 if (auto v = "autoregister" in request) 102 autoRegister = v.type != JSON_TYPE.FALSE; 103 string[] allComponents; 104 static foreach (Component; AllComponents) 105 allComponents ~= getUDAs!(Component, ComponentInfo)[0].name; 106 ComponentSwitch: 107 switch (request["component"].str) 108 { 109 static foreach (Component; AllComponents) 110 { 111 case getUDAs!(Component, ComponentInfo)[0].name: 112 engine.register!Component(autoRegister); 113 break ComponentSwitch; 114 } 115 default: 116 sendException(id, 117 new Exception( 118 "Unknown Component '" ~ request["component"].str ~ "', built-in are " ~ allComponents.join( 119 ", "))); 120 return; 121 } 122 sendResponse(id, JSONValue(true)); 123 } 124 } 125 else if (request["cmd"].str == "new") 126 { 127 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 128 { 129 sendException(id, 130 new Exception( 131 `Expected new message to be in format {"cmd":"new", "cwd":string, ("config":object)}`)); 132 } 133 else 134 { 135 string cwd = request["cwd"].str; 136 if ("config" in request) 137 engine.addInstance(cwd, Configuration(request["config"])); 138 else 139 engine.addInstance(cwd); 140 sendResponse(id, JSONValue(true)); 141 } 142 } 143 else if (request["cmd"].str == "config-set") 144 { 145 if ("config" !in request || request["config"].type != JSON_TYPE.OBJECT) 146 { 147 configSetFail: 148 sendException(id, 149 new Exception( 150 `Expected new message to be in format {"cmd":"config-set", ("cwd":string), "config":object}`)); 151 } 152 else 153 { 154 if ("cwd" in request) 155 { 156 if (request["cwd"].type != JSON_TYPE.STRING) 157 goto configSetFail; 158 else 159 engine.getInstance(request["cwd"].str).config.base = request["config"]; 160 } 161 else 162 engine.globalConfiguration.base = request["config"]; 163 sendResponse(id, JSONValue(true)); 164 } 165 } 166 else if (request["cmd"].str == "config-get") 167 { 168 if ("cwd" in request) 169 { 170 if (request["cwd"].type != JSON_TYPE.STRING) 171 sendException(id, 172 new Exception( 173 `Expected new message to be in format {"cmd":"config-get", ("cwd":string)}`)); 174 else 175 sendResponse(id, engine.getInstance(request["cwd"].str).config.base); 176 } 177 else 178 sendResponse(id, engine.globalConfiguration.base); 179 } 180 else if (request["cmd"].str == "call") 181 { 182 JSONValue[] params; 183 if ("params" in request) 184 { 185 if (request["params"].type != JSON_TYPE.ARRAY) 186 goto callFail; 187 params = request["params"].array; 188 } 189 if ("method" !in request || request["method"].type != JSON_TYPE.STRING 190 || "component" !in request || request["component"].type != JSON_TYPE.STRING) 191 { 192 callFail: 193 sendException(id, new Exception(`Expected call message to be in format {"cmd":"call", "component":string, "method":string, ("cwd":string), ("params":object[])}`)); 194 } 195 else 196 { 197 Future!JSONValue ret; 198 string component = request["component"].str; 199 string method = request["method"].str; 200 if ("cwd" in request) 201 { 202 if (request["cwd"].type != JSON_TYPE.STRING) 203 { 204 goto callFail; 205 } 206 else 207 { 208 string cwd = request["cwd"].str; 209 ret = engine.run(cwd, component, method, params); 210 } 211 } 212 else 213 ret = engine.run(component, method, params); 214 215 ret.onDone = { 216 if (ret.exception) 217 sendException(id, ret.exception); 218 else 219 sendResponse(id, ret.value); 220 }; 221 } 222 } 223 else if (request["cmd"].str == "import-paths") 224 { 225 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 226 sendException(id, 227 new Exception(`Expected new message to be in format {"cmd":"import-paths", "cwd":string}`)); 228 else 229 sendResponse(id, engine.getInstance(request["cwd"].str).importPaths.toJSON); 230 } 231 else if (request["cmd"].str == "import-files") 232 { 233 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 234 sendException(id, 235 new Exception(`Expected new message to be in format {"cmd":"import-files", "cwd":string}`)); 236 else 237 sendResponse(id, engine.getInstance(request["cwd"].str).importFiles.toJSON); 238 } 239 else if (request["cmd"].str == "string-import-paths") 240 { 241 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 242 sendException(id, 243 new Exception( 244 `Expected new message to be in format {"cmd":"string-import-paths", "cwd":string}`)); 245 else 246 sendResponse(id, engine.getInstance(request["cwd"].str).stringImportPaths.toJSON); 247 } 248 else 249 { 250 printUsage: 251 sendException(id, new Exception("Invalid request, must contain a cmd string key with one of the values [version, load, new, config-get, config-set, call, import-paths, import-files, string-import-paths]")); 252 } 253 } 254 255 void processException(int id, Throwable e) 256 { 257 stderr.writeln(e); 258 // dfmt off 259 sendResponse(id, JSONValue([ 260 "error": JSONValue(true), 261 "msg": JSONValue(e.msg), 262 "exception": JSONValue(e.toString()) 263 ])); 264 // dfmt on 265 } 266 267 void processException(int id, JSONValue request, Throwable e) 268 { 269 stderr.writeln(e); 270 // dfmt off 271 sendResponse(id, JSONValue([ 272 "error": JSONValue(true), 273 "msg": JSONValue(e.msg), 274 "exception": JSONValue(e.toString()), 275 "request": request 276 ])); 277 // dfmt on 278 } 279 280 int main(string[] args) 281 { 282 import std.file; 283 import etc.linux.memoryerror; 284 285 version (unittest) 286 { 287 } 288 else 289 { 290 version (DigitalMars) 291 static if (is(typeof(registerMemoryErrorHandler))) 292 registerMemoryErrorHandler(); 293 294 if (args.length > 1 && (args[1] == "-v" || args[1] == "--version" || args[1] == "-version")) 295 { 296 stdout.writeln(getVersionInfoString); 297 return 0; 298 } 299 300 engine = new WorkspaceD(); 301 engine.onBroadcast = (&broadcast).toDelegate; 302 engine.onBindFail = (&bindFail).toDelegate; 303 scope (exit) 304 engine.shutdown(); 305 306 writeMutex = new Mutex; 307 commandMutex = new Mutex; 308 309 int length = 0; 310 int id = 0; 311 ubyte[4] intBuffer; 312 ubyte[] dataBuffer; 313 JSONValue data; 314 315 scope (exit) 316 handleRequest(int.min, JSONValue(["cmd" : "unload", "components" : "*"])); 317 318 stderr.writeln("Config files stored in ", standardPaths(StandardPath.config, "workspace-d")); 319 320 while (stdin.isOpen && stdout.isOpen && !stdin.eof) 321 { 322 dataBuffer = stdin.rawRead(intBuffer); 323 assert(dataBuffer.length == 4, "Unexpected buffer data"); 324 length = bigEndianToNative!int(dataBuffer[0 .. 4]); 325 326 assert(length >= 4, "Invalid request"); 327 328 dataBuffer = stdin.rawRead(intBuffer); 329 assert(dataBuffer.length == 4, "Unexpected buffer data"); 330 id = bigEndianToNative!int(dataBuffer[0 .. 4]); 331 332 dataBuffer.length = length - 4; 333 dataBuffer = stdin.rawRead(dataBuffer); 334 335 try 336 { 337 data = parseJSON(cast(string) dataBuffer); 338 } 339 catch (Exception e) 340 { 341 processException(id, e); 342 } 343 catch (AssertError e) 344 { 345 processException(id, e); 346 } 347 348 try 349 { 350 handleRequest(id, data); 351 } 352 catch (Exception e) 353 { 354 processException(id, data, e); 355 } 356 catch (AssertError e) 357 { 358 processException(id, data, e); 359 } 360 stdout.flush(); 361 } 362 } 363 return 0; 364 }