PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/extensions/sciviewsk/content/js/socket.js

https://bitbucket.org/activestate/komodo-3rdparty
JavaScript | 421 lines | 283 code | 48 blank | 90 comment | 51 complexity | a42fb774bea63d545448187bbb4a0cf9 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception
  1. // SciViews-K socket client/server functions
  2. // Socket client and server functions to connect to R kernels
  3. // Copyright (c) 2008-2009, Ph. Grosjean (phgrosjean@sciviews.org)
  4. // License: MPL 1.1/GPL 2.0/LGPL 2.1
  5. ////////////////////////////////////////////////////////////////////////////////
  6. // To cope with versions incompatibilities, we define this:
  7. // sv.socket.svSocketMinVersion // minimal svSocket R package version required
  8. //
  9. /////// Socket client //////
  10. // Parameters:
  11. // sv.socket.host; // The address of the R server host (local only for now)
  12. // sv.socket.cmdout // Do we echo exchange to the Output Command pane?
  13. // sv.socket.prompt // Look at this to know if we are in multiline mode
  14. // sv.socket.cmd // In case of multiline mode, the partial command so far
  15. //
  16. // sv.socket.rClient(host, port, outputData, listener, echo); // Main client fct
  17. // sv.socket.rCommand(cmd, echo, echofun, procfun, ...); // Send cmd to the R socket server for evaluation
  18. //
  19. /////// Socket server //////
  20. // Parameters:
  21. // sv.socket.debug // Debugging mode (in Command Output)
  22. // sv.socket.serverIsLocal // Is the socket servicing only localhost?
  23. //
  24. // sv.socket.serverStart(); // Start the SciViews-K socket server
  25. // sv.socket.serverStop(); // Stop the SciViews-K socket server
  26. // sv.socket.serverIsStarted(); // Is the socket server currently started?
  27. // sv.socket.serverConfig(); // Get a short description of server config
  28. // sv.socket.serverWrite(data); // Write a string through the socket server
  29. // sv.socket.updateCharset(force); // Update R character set
  30. ////////////////////////////////////////////////////////////////////////////////
  31. //
  32. // TODO: use svSocketMinVersion to check svSocket R package is not too old!
  33. // TODO: start the socket server automatically when Komodo is launched
  34. // Define the 'sv.socket' namespace
  35. if (typeof(sv.socket) == 'undefined')
  36. sv.socket = {};
  37. (function () {
  38. /////// Socket client //////////////////////////////////////////////////////
  39. this.svSocketMinVersion = "0.9-48"; // Will be used later for compatibility
  40. // checking between R and Komodo tools
  41. this.host = "127.0.0.1"; // Host to connect to (local host only, currently)
  42. this.cmdout = true; // Do we write to 'Command Output'?
  43. this.prompt = ":> "; // The prompt, could be changed to continue prompt
  44. this.cmd = ""; // The command to send to R
  45. var millis = 500; // ms to wait for input, with synchroneous com only
  46. var _charsetUpdated = false;
  47. var _this = this;
  48. var converter = Components
  49. .classes["@mozilla.org/intl/scriptableunicodeconverter"]
  50. .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  51. // The main socket client function to connect to R socket server
  52. this.rClient = function (host, port, outputData, listener, echo, echofun) {
  53. try {
  54. var transportService = Components.
  55. classes["@mozilla.org/network/socket-transport-service;1"]
  56. .getService(Components.interfaces.nsISocketTransportService);
  57. var transport = transportService.createTransport(null, 0,
  58. host, port, null);
  59. if (converter.charset) try {
  60. // Convert output string from unicode to R's charset (Bug #240)
  61. outputData = converter.ConvertFromUnicode(outputData);
  62. } catch(e) {
  63. sv.log.exception(e, "sv.socket.rClient() is unable to convert" +
  64. " from Unicode");
  65. }
  66. var outstream = transport.openOutputStream(0, 0, 0);
  67. outstream.write(outputData, outputData.length);
  68. var stream = transport.openInputStream(0, 0, 0);
  69. var instream = Components.
  70. classes["@mozilla.org/scriptableinputstream;1"]
  71. .createInstance(Components.interfaces.nsIScriptableInputStream);
  72. instream.init(stream);
  73. var dataListener = {
  74. data: "",
  75. onStartRequest: function(request, context) { this.data = ""; },
  76. onStopRequest: function(request, context, status) {
  77. instream.close();
  78. outstream.close();
  79. this.data = sv.tools.strings.removeLastCRLF(this.data);
  80. listener.finished(this.data);
  81. },
  82. onDataAvailable: function(request, context,
  83. inputStream, offset, count) {
  84. // TODO: limit the amount of data send through the socket!
  85. var chunk = instream.read(count);
  86. if (converter.charset)
  87. try { // Convert read string to unicode (Bug #240)
  88. chunk = converter.ConvertToUnicode(chunk);
  89. } catch(e) {
  90. sv.log.exception(e, "sv.socket.rClient() is" +
  91. " unable to convert to Unicode");
  92. }
  93. // Determine if we have a prompt at the end
  94. if (chunk.search(/\+\s+$/) > -1) {
  95. this.prompt = ":+ ";
  96. // remove endline from prompt if it is a continuation
  97. chunk = chunk.rtrim() + " ";
  98. } else if (chunk.search(/>\s+$/) > -1) {
  99. this.prompt = ":> ";
  100. }
  101. // Do we need to close the connection
  102. // (\f received, followed by \n, \r, or both)?
  103. if (chunk.match("\n\f") == "\n\f") {
  104. instream.close();
  105. outstream.close();
  106. // Eliminate trailing (\r)\n\f chars before the prompt
  107. // Eliminate the last carriage return after the prompt
  108. chunk = chunk.replace(/(\r?\n\f|\s+$)/, "");
  109. }
  110. this.data += chunk;
  111. // Do we "echo" these results somewhere?
  112. if (echo) {
  113. if (echofun == null) {
  114. // Use default echo function (to the Command Output)
  115. sv.cmdout.append(chunk, newline = false);
  116. } else echofun(chunk);
  117. }
  118. }
  119. }
  120. var pump = Components.
  121. classes["@mozilla.org/network/input-stream-pump;1"].
  122. createInstance(Components.interfaces.nsIInputStreamPump);
  123. pump.init(stream, -1, -1, 0, 0, false);
  124. pump.asyncRead(dataListener, null);
  125. } catch (e) {
  126. sv.log.exception(e, "sv.socket.rClient() raises an unknown error");
  127. return(e);
  128. }
  129. return(null);
  130. }
  131. // Send an R command through the socket
  132. // any additional arguments will be passed to procfun
  133. // procfun can be also an object, then the result will be stored in procfun.value
  134. this.rCommand = function(cmd, echo, echofun, procfun) {
  135. cmd = sv.tools.strings.replaceCRLF(cmd, "<<<n>>>");
  136. var listener;
  137. if (procfun == null) { // Do nothing at the end
  138. listener = { finished: function(data) {} }
  139. } else {
  140. // Call procfun at the end
  141. // convert all arguments to an Array
  142. var args = Array.apply(null, arguments);
  143. listener = {
  144. finished: function(data) {
  145. // keep only arguments after procfun, and add "data"
  146. args.splice(0, 4, data);
  147. if (typeof procfun == "function") {
  148. procfun.apply(null, args);
  149. } else {
  150. //in fact we can add a property even to a function
  151. procfun.value = data;
  152. }
  153. }
  154. }
  155. }
  156. // TODO: deal with error checking for this command
  157. var port = sv.prefs.getString("sciviews.client.socket", "8888");
  158. var id = "<<<id=" +
  159. sv.prefs.getString("sciviews.client.id", "SciViewsK") + ">>>";
  160. var res = this.rClient(this.host, port, id + cmd + "\n",
  161. listener, echo, echofun);
  162. // if exception was returned:
  163. if (res && res.name && res.name == "NS_ERROR_OFFLINE") {
  164. ko.statusBar.AddMessage("Error: R is unreachable (see log)", "R",
  165. 5000, true);
  166. }
  167. return(res);
  168. }
  169. //Test: sv.socket.rCommand("<<<q>>>cat('library = '); str(library)");
  170. /////// Socket server //////////////////////////////////////////////////////
  171. this.debug = sv.log.isAll(); // Set to true for debugging mode
  172. this.serverIsLocal = true; // Is the socket servicing only localhost?
  173. const nsITransport = Components.interfaces.nsITransport;
  174. var _serverSocket; // The SviViews-K socket server object
  175. var _serverStarted = false; // Is the socket server started?
  176. var _inputString; // The string with command send by R
  177. var _outputString; // The string with the result to send to R
  178. var _output = [];
  179. //debug only:
  180. this.serverSocket = _serverSocket;
  181. // Core function for the SciViews-K socket server:
  182. // create the _serverSocket object
  183. this.serverStart = function () {
  184. if (this.debug) sv.log.debug("Socket server: serverStart");
  185. if (_serverStarted) try {
  186. _serverSocket.close();
  187. _serverStarted = false;
  188. } catch(e) {
  189. sv.log.exception(e, "sv.socket.serverStart() failed to close the" +
  190. " socket before reopening it");
  191. }
  192. var listener = {
  193. onSocketAccepted : function (socket, transport) {
  194. try {
  195. if (this.debug)
  196. sv.log.debug("Socket server: onSocketAccepted!");
  197. // Make sure to clean input and output strings before use
  198. _inputString = "";
  199. _outputString = "";
  200. _output = [];
  201. if (this.debug)
  202. sv.log.debug("Socket server: " + transport.host +
  203. " on port " + transport.port + "\n");
  204. // Then, read data from the client
  205. var inputStream = transport.openInputStream(nsITransport.
  206. OPEN_BLOCKING, 0, 0);
  207. var sin = Components.
  208. classes["@mozilla.org/scriptableinputstream;1"]
  209. .createInstance(Components.interfaces.
  210. nsIScriptableInputStream);
  211. sin.init(inputStream);
  212. var date = new Date();
  213. do {
  214. _inputString = sin.read(512);
  215. } while(_inputString == "" && ((new Date()) - date < millis))
  216. // Read the complete data
  217. while (sin.available() > 0)
  218. _inputString += sin.read(512);
  219. if (converter.charset) try {
  220. _inputString = converter.ConvertToUnicode(_inputString);
  221. } catch (e) {
  222. sv.log.exception(e, "Socket server: onSocketAccepted()" +
  223. " is unable to convert to Unicode");
  224. }
  225. // Is there data send?
  226. if (_inputString == "") {
  227. //_outputString += "Error: no command send!\n"
  228. _output.push("Error: no command send!");
  229. } else {
  230. // Process the command
  231. if (this.debug)
  232. sv.log.debug("Command send by the client:\n" +
  233. _inputString);
  234. try {
  235. eval(_inputString);
  236. } catch(e) {
  237. _output.push(e.toString());
  238. }
  239. }
  240. if (this.debug) sv.log.debug(_output.length?
  241. ("Result:\n" + _output.join("\n")) :
  242. ("Nothing to return to the socket client")
  243. );
  244. // And finally, return the result to the socket client
  245. // (append \n at the end)
  246. _outputString = _output.join("\n") + "\n";
  247. if (converter.charset) try {
  248. _outputString = converter.
  249. ConvertFromUnicode(_outputString);
  250. } catch(e) {
  251. sv.log.exception(e, "Socket server: onSocketAccepted()" +
  252. " is unable to convert from Unicode");
  253. }
  254. var outputStream = transport.openOutputStream(nsITransport.
  255. OPEN_BLOCKING, 0, 0);
  256. outputStream.write(_outputString, _outputString.length);
  257. } catch(e) {
  258. sv.log.exception(e, "Socket server: onSocketAccepted()," +
  259. " unmanaged error");
  260. } finally {
  261. // Make sure that streams are closed
  262. outputStream.close();
  263. inputStream.close();
  264. if (this.debug) sv.log.debug("SocketAccepted: end");
  265. }
  266. },
  267. onStopListening : function (socket, status) {
  268. // The connection is closed by the client
  269. if (this.debug) sv.log.debug("Socket server closed");
  270. }
  271. };
  272. try {
  273. _serverSocket = Components.
  274. classes["@mozilla.org/network/server-socket;1"]
  275. .createInstance(Components.interfaces.nsIServerSocket);
  276. var port = sv.prefs.getString("sciviews.server.socket", "7052");
  277. _serverSocket.init(port, this.serverIsLocal, -1);
  278. _serverSocket.asyncListen(listener);
  279. _serverStarted = true;
  280. } catch(e) {
  281. sv.log.exception(e, "SciViews-K cannot open a server socket on port " +
  282. port + ".\nMake sure the port is not already used by another" +
  283. " Komodo instance" + "\nor choose another port in the" +
  284. " preferences and restart Komodo", false);
  285. // If the port is already used, I got:
  286. // "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame ::
  287. // chrome://sciviewsk/content/js/socket.js :: anonymous :: line 285
  288. }
  289. if (this.debug) sv.log.debug("Socket server started");
  290. ko.statusBar.AddMessage("SciViews-K socket server started", "svSock",
  291. 2000, true);
  292. }
  293. // Stop the SciViews-K socket server
  294. this.serverStop = function () {
  295. if (_serverStarted) {
  296. try {
  297. _serverSocket.close();
  298. } catch(e) {
  299. sv.log.exception(e, "Socket server: serverStop() cannot" +
  300. " close the socket", true);
  301. }
  302. _serverStarted = false;
  303. if (this.debug) sv.log.debug("Socket server stopped");
  304. ko.statusBar.AddMessage("SciViews-K socket server stopped",
  305. "svSock", 2000, true);
  306. } else {
  307. ko.statusBar.AddMessage("SciViews-K socket server is not started",
  308. "svSock", 2000, true);
  309. }
  310. }
  311. // Is the SciViews-K socket server started?
  312. this.serverIsStarted = function () {
  313. return(_serverStarted);
  314. }
  315. // What is the current SciViews-K socket server config?
  316. this.serverConfig = function () {
  317. var serverStatus = " (stopped)"
  318. if (_serverStarted) serverStatus = " (started)"
  319. var port = sv.prefs.getString("sciviews.server.socket", "7052");
  320. if (this.serverIsLocal) {
  321. return("Local socket server on port " + port + serverStatus);
  322. } else {
  323. return("Global socket server on port " + port + serverStatus);
  324. }
  325. }
  326. // Write to the socket server, use this to return something to the client
  327. this.serverWrite = function (data) {
  328. if (_serverStarted) {
  329. _output.push(data);
  330. } else {
  331. sv.alert("The socket server in unavailable",
  332. "Trying to write data though the SciViews-K socket server" +
  333. " that is not started!");
  334. }
  335. }
  336. this.__defineGetter__ ('charset', function () {
  337. return converter.charset;
  338. });
  339. this.__defineSetter__ ('charset', function (x) {
  340. try {
  341. converter.charset = x;
  342. } catch (e) {
  343. _charsetUpdated = false;
  344. }
  345. return converter.charset;
  346. });
  347. this.updateCharset = function (force) {
  348. if (!force && _charsetUpdated) return;
  349. //if (this.debug) sv.log.debug("charsetUpdate");
  350. _charsetUpdated = true;
  351. // We also make sure that dec and sep are synched in R
  352. _this.rCommand('<<<h>>>options(OutDec = ' +
  353. sv.prefs.getString("r.csv.dec.arg", ".") +
  354. '); options(OutSep = ' +
  355. sv.prefs.getString("r.csv.sep.arg", ",") +
  356. '); invisible(guiRefresh(force = TRUE)); cat(localeToCharset()[1])',
  357. false, null, function (s) {
  358. _this.charset = s;
  359. if (this.debug) sv.log.debug(s);
  360. });
  361. // Update also the R Object browser and active objects lists
  362. //sv.r.objects.getPackageList(true); // old code refreshing only object browser
  363. // New code is: sv.r.eval("guiRefresh(force = TRUE)");
  364. // ... and it is integrated in the command above!
  365. }
  366. // [PhG] The following command raises an error on my Mac
  367. //window.setTimeout(this.serverStart, 500);
  368. //window.setTimeout(this.updateCharset, 100);
  369. //this.charset = 'cp1250';
  370. }).apply(sv.socket);
  371. // [PhG] This raises an error on Komodo 5.1 on Mac OS X
  372. //addEventListener("load", sv.socket.serverStart, false);
  373. window.setTimeout(sv.socket.serverStart, 500);