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 WorkspaceD engine; 70 71 void handleRequest(int id, JSONValue request) 72 { 73 if (request.type != JSON_TYPE.OBJECT || "cmd" !in request 74 || request["cmd"].type != JSON_TYPE.STRING) 75 { 76 goto printUsage; 77 } 78 else if (request["cmd"].str == "version") 79 { 80 sendResponse(id, getVersionInfoJson); 81 } 82 else if (request["cmd"].str == "load") 83 { 84 if ("component" !in request || request["component"].type != JSON_TYPE.STRING) 85 { 86 sendException(id, 87 new Exception( 88 `Expected load message to be in format {"cmd":"load", "component":string, ("autoregister":bool)}`)); 89 } 90 else 91 { 92 bool autoRegister = true; 93 if (auto v = "autoregister" in request) 94 autoRegister = v.type != JSON_TYPE.FALSE; 95 string[] allComponents; 96 static foreach (Component; AllComponents) 97 allComponents ~= getUDAs!(Component, ComponentInfo)[0].name; 98 ComponentSwitch: 99 switch (request["component"].str) 100 { 101 static foreach (Component; AllComponents) 102 { 103 case getUDAs!(Component, ComponentInfo)[0].name: 104 engine.register!Component(autoRegister); 105 break ComponentSwitch; 106 } 107 default: 108 sendException(id, 109 new Exception( 110 "Unknown Component '" ~ request["component"].str ~ "', built-in are " ~ allComponents.join( 111 ", "))); 112 return; 113 } 114 sendResponse(id, JSONValue(true)); 115 } 116 } 117 else if (request["cmd"].str == "new") 118 { 119 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 120 { 121 sendException(id, 122 new Exception( 123 `Expected new message to be in format {"cmd":"new", "cwd":string, ("config":object)}`)); 124 } 125 else 126 { 127 string cwd = request["cwd"].str; 128 if ("config" in request) 129 engine.addInstance(cwd, Configuration(request["config"])); 130 else 131 engine.addInstance(cwd); 132 sendResponse(id, JSONValue(true)); 133 } 134 } 135 else if (request["cmd"].str == "config-set") 136 { 137 if ("config" !in request || request["config"].type != JSON_TYPE.OBJECT) 138 { 139 configSetFail: 140 sendException(id, 141 new Exception( 142 `Expected new message to be in format {"cmd":"config-set", ("cwd":string), "config":object}`)); 143 } 144 else 145 { 146 if ("cwd" in request) 147 { 148 if (request["cwd"].type != JSON_TYPE.STRING) 149 goto configSetFail; 150 else 151 engine.getInstance(request["cwd"].str).config.base = request["config"]; 152 } 153 else 154 engine.globalConfiguration.base = request["config"]; 155 sendResponse(id, JSONValue(true)); 156 } 157 } 158 else if (request["cmd"].str == "config-get") 159 { 160 if ("cwd" in request) 161 { 162 if (request["cwd"].type != JSON_TYPE.STRING) 163 sendException(id, 164 new Exception( 165 `Expected new message to be in format {"cmd":"config-get", ("cwd":string)}`)); 166 else 167 sendResponse(id, engine.getInstance(request["cwd"].str).config.base); 168 } 169 else 170 sendResponse(id, engine.globalConfiguration.base); 171 } 172 else if (request["cmd"].str == "call") 173 { 174 JSONValue[] params; 175 if ("params" in request) 176 { 177 if (request["params"].type != JSON_TYPE.ARRAY) 178 goto callFail; 179 params = request["params"].array; 180 } 181 if ("method" !in request || request["method"].type != JSON_TYPE.STRING 182 || "component" !in request || request["component"].type != JSON_TYPE.STRING) 183 { 184 callFail: 185 sendException(id, new Exception(`Expected call message to be in format {"cmd":"call", "component":string, "method":string, ("cwd":string), ("params":object[])}`)); 186 } 187 else 188 { 189 Future!JSONValue ret; 190 string component = request["component"].str; 191 string method = request["method"].str; 192 if ("cwd" in request) 193 { 194 if (request["cwd"].type != JSON_TYPE.STRING) 195 { 196 goto callFail; 197 } 198 else 199 { 200 string cwd = request["cwd"].str; 201 ret = engine.run(cwd, component, method, params); 202 } 203 } 204 else 205 ret = engine.run(component, method, params); 206 207 ret.onDone = { 208 if (ret.exception) 209 sendException(id, ret.exception); 210 else 211 sendResponse(id, ret.value); 212 }; 213 } 214 } 215 else if (request["cmd"].str == "import-paths") 216 { 217 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 218 sendException(id, 219 new Exception(`Expected new message to be in format {"cmd":"import-paths", "cwd":string}`)); 220 else 221 sendResponse(id, engine.getInstance(request["cwd"].str).importPaths.toJSON); 222 } 223 else if (request["cmd"].str == "import-files") 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-files", "cwd":string}`)); 228 else 229 sendResponse(id, engine.getInstance(request["cwd"].str).importFiles.toJSON); 230 } 231 else if (request["cmd"].str == "string-import-paths") 232 { 233 if ("cwd" !in request || request["cwd"].type != JSON_TYPE.STRING) 234 sendException(id, 235 new Exception( 236 `Expected new message to be in format {"cmd":"string-import-paths", "cwd":string}`)); 237 else 238 sendResponse(id, engine.getInstance(request["cwd"].str).stringImportPaths.toJSON); 239 } 240 else 241 { 242 printUsage: 243 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]")); 244 } 245 } 246 247 void processException(int id, Throwable e) 248 { 249 stderr.writeln(e); 250 // dfmt off 251 sendResponse(id, JSONValue([ 252 "error": JSONValue(true), 253 "msg": JSONValue(e.msg), 254 "exception": JSONValue(e.toString()) 255 ])); 256 // dfmt on 257 } 258 259 void processException(int id, JSONValue request, Throwable e) 260 { 261 stderr.writeln(e); 262 // dfmt off 263 sendResponse(id, JSONValue([ 264 "error": JSONValue(true), 265 "msg": JSONValue(e.msg), 266 "exception": JSONValue(e.toString()), 267 "request": request 268 ])); 269 // dfmt on 270 } 271 272 int main(string[] args) 273 { 274 import std.file; 275 import etc.linux.memoryerror; 276 277 version (unittest) 278 { 279 } 280 else 281 { 282 version (DigitalMars) 283 static if (is(typeof(registerMemoryErrorHandler))) 284 registerMemoryErrorHandler(); 285 286 if (args.length > 1 && (args[1] == "-v" || args[1] == "--version" || args[1] == "-version")) 287 { 288 stdout.writeln(getVersionInfoString); 289 return 0; 290 } 291 292 engine = new WorkspaceD(); 293 engine.onBroadcast = (&broadcast).toDelegate; 294 scope (exit) 295 engine.shutdown(); 296 297 writeMutex = new Mutex; 298 commandMutex = new Mutex; 299 300 int length = 0; 301 int id = 0; 302 ubyte[4] intBuffer; 303 ubyte[] dataBuffer; 304 JSONValue data; 305 306 scope (exit) 307 handleRequest(int.min, JSONValue(["cmd" : "unload", "components" : "*"])); 308 309 stderr.writeln("Config files stored in ", standardPaths(StandardPath.config, "workspace-d")); 310 311 while (stdin.isOpen && stdout.isOpen && !stdin.eof) 312 { 313 dataBuffer = stdin.rawRead(intBuffer); 314 assert(dataBuffer.length == 4, "Unexpected buffer data"); 315 length = bigEndianToNative!int(dataBuffer[0 .. 4]); 316 317 assert(length >= 4, "Invalid request"); 318 319 dataBuffer = stdin.rawRead(intBuffer); 320 assert(dataBuffer.length == 4, "Unexpected buffer data"); 321 id = bigEndianToNative!int(dataBuffer[0 .. 4]); 322 323 dataBuffer.length = length - 4; 324 dataBuffer = stdin.rawRead(dataBuffer); 325 326 try 327 { 328 data = parseJSON(cast(string) dataBuffer); 329 } 330 catch (Exception e) 331 { 332 processException(id, e); 333 } 334 catch (AssertError e) 335 { 336 processException(id, e); 337 } 338 339 try 340 { 341 handleRequest(id, data); 342 } 343 catch (Exception e) 344 { 345 processException(id, data, e); 346 } 347 catch (AssertError e) 348 { 349 processException(id, data, e); 350 } 351 stdout.flush(); 352 } 353 } 354 return 0; 355 }