##// END OF EJS Templates
add metadata to javascript msg spec implementation
MinRK -
Show More
@@ -1,390 +1,391 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 // Initialization and connection.
17 17
18 18 var Kernel = function (base_url) {
19 19 this.kernel_id = null;
20 20 this.shell_channel = null;
21 21 this.iopub_channel = null;
22 22 this.base_url = base_url;
23 23 this.running = false;
24 24 this.username = "username";
25 25 this.session_id = utils.uuid();
26 26 this._msg_callbacks = {};
27 27
28 28 if (typeof(WebSocket) !== 'undefined') {
29 29 this.WebSocket = WebSocket;
30 30 } else if (typeof(MozWebSocket) !== 'undefined') {
31 31 this.WebSocket = MozWebSocket;
32 32 } else {
33 33 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
34 34 };
35 35 };
36 36
37 37
38 38 Kernel.prototype._get_msg = function (msg_type, content) {
39 39 var msg = {
40 40 header : {
41 41 msg_id : utils.uuid(),
42 42 username : this.username,
43 43 session : this.session_id,
44 44 msg_type : msg_type
45 45 },
46 metadata : {},
46 47 content : content,
47 48 parent_header : {}
48 49 };
49 50 return msg;
50 51 };
51 52
52 53 Kernel.prototype.start = function (notebook_id) {
53 54 var that = this;
54 55 if (!this.running) {
55 56 var qs = $.param({notebook:notebook_id});
56 57 var url = this.base_url + '?' + qs;
57 58 $.post(url,
58 59 $.proxy(that._kernel_started,that),
59 60 'json'
60 61 );
61 62 };
62 63 };
63 64
64 65
65 66 Kernel.prototype.restart = function () {
66 67 $([IPython.events]).trigger('status_restarting.Kernel');
67 68 var that = this;
68 69 if (this.running) {
69 70 this.stop_channels();
70 71 var url = this.kernel_url + "/restart";
71 72 $.post(url,
72 73 $.proxy(that._kernel_started, that),
73 74 'json'
74 75 );
75 76 };
76 77 };
77 78
78 79
79 80 Kernel.prototype._kernel_started = function (json) {
80 81 console.log("Kernel started: ", json.kernel_id);
81 82 this.running = true;
82 83 this.kernel_id = json.kernel_id;
83 84 this.ws_url = json.ws_url;
84 85 this.kernel_url = this.base_url + "/" + this.kernel_id;
85 86 this.start_channels();
86 87 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
87 88 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
88 89 };
89 90
90 91
91 92 Kernel.prototype._websocket_closed = function(ws_url, early){
92 93 var msg;
93 94 var parent_item = $('body');
94 95 if (early) {
95 96 msg = "Websocket connection to " + ws_url + " could not be established." +
96 97 " You will NOT be able to run code." +
97 98 " Your browser may not be compatible with the websocket version in the server," +
98 99 " or if the url does not look right, there could be an error in the" +
99 100 " server's configuration.";
100 101 } else {
101 102 IPython.notification_widget.set_message('Reconnecting Websockets', 1000);
102 103 this.start_channels();
103 104 return;
104 105 }
105 106 var dialog = $('<div/>');
106 107 dialog.html(msg);
107 108 parent_item.append(dialog);
108 109 dialog.dialog({
109 110 resizable: false,
110 111 modal: true,
111 112 title: "Websocket closed",
112 113 closeText: "",
113 114 close: function(event, ui) {$(this).dialog('destroy').remove();},
114 115 buttons : {
115 116 "OK": function () {
116 117 $(this).dialog('close');
117 118 }
118 119 }
119 120 });
120 121
121 122 };
122 123
123 124 Kernel.prototype.start_channels = function () {
124 125 var that = this;
125 126 this.stop_channels();
126 127 var ws_url = this.ws_url + this.kernel_url;
127 128 console.log("Starting WS:", ws_url);
128 129 this.shell_channel = new this.WebSocket(ws_url + "/shell");
129 130 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
130 131 send_cookie = function(){
131 132 this.send(document.cookie);
132 133 };
133 134 var already_called_onclose = false; // only alert once
134 135 ws_closed_early = function(evt){
135 136 if (already_called_onclose){
136 137 return;
137 138 }
138 139 already_called_onclose = true;
139 140 if ( ! evt.wasClean ){
140 141 that._websocket_closed(ws_url, true);
141 142 }
142 143 };
143 144 ws_closed_late = function(evt){
144 145 if (already_called_onclose){
145 146 return;
146 147 }
147 148 already_called_onclose = true;
148 149 if ( ! evt.wasClean ){
149 150 that._websocket_closed(ws_url, false);
150 151 }
151 152 };
152 153 this.shell_channel.onopen = send_cookie;
153 154 this.shell_channel.onclose = ws_closed_early;
154 155 this.iopub_channel.onopen = send_cookie;
155 156 this.iopub_channel.onclose = ws_closed_early;
156 157 // switch from early-close to late-close message after 1s
157 158 setTimeout(function(){
158 159 that.shell_channel.onclose = ws_closed_late;
159 160 that.iopub_channel.onclose = ws_closed_late;
160 161 }, 1000);
161 162 };
162 163
163 164
164 165 Kernel.prototype.stop_channels = function () {
165 166 if (this.shell_channel !== null) {
166 167 this.shell_channel.onclose = function (evt) {};
167 168 this.shell_channel.close();
168 169 this.shell_channel = null;
169 170 };
170 171 if (this.iopub_channel !== null) {
171 172 this.iopub_channel.onclose = function (evt) {};
172 173 this.iopub_channel.close();
173 174 this.iopub_channel = null;
174 175 };
175 176 };
176 177
177 178 // Main public methods.
178 179
179 180 Kernel.prototype.object_info_request = function (objname, callbacks) {
180 181 // When calling this method pass a callbacks structure of the form:
181 182 //
182 183 // callbacks = {
183 184 // 'object_info_reply': object_into_reply_callback
184 185 // }
185 186 //
186 187 // The object_info_reply_callback will be passed the content object of the
187 188 // object_into_reply message documented here:
188 189 //
189 190 // http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
190 191 if(typeof(objname)!=null && objname!=null)
191 192 {
192 193 var content = {
193 194 oname : objname.toString(),
194 195 };
195 196 var msg = this._get_msg("object_info_request", content);
196 197 this.shell_channel.send(JSON.stringify(msg));
197 198 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
198 199 return msg.header.msg_id;
199 200 }
200 201 return;
201 202 }
202 203
203 204 Kernel.prototype.execute = function (code, callbacks, options) {
204 205 // The options object should contain the options for the execute call. Its default
205 206 // values are:
206 207 //
207 208 // options = {
208 209 // silent : true,
209 210 // user_variables : [],
210 211 // user_expressions : {},
211 212 // allow_stdin : false
212 213 // }
213 214 //
214 215 // When calling this method pass a callbacks structure of the form:
215 216 //
216 217 // callbacks = {
217 218 // 'execute_reply': execute_reply_callback,
218 219 // 'output': output_callback,
219 220 // 'clear_output': clear_output_callback,
220 221 // 'set_next_input': set_next_input_callback
221 222 // }
222 223 //
223 224 // The execute_reply_callback will be passed the content and metadata objects of the execute_reply
224 225 // message documented here:
225 226 //
226 227 // http://ipython.org/ipython-doc/dev/development/messaging.html#execute
227 228 //
228 229 // The output_callback will be passed msg_type ('stream','display_data','pyout','pyerr')
229 230 // of the output and the content and metadata objects of the PUB/SUB channel that contains the
230 231 // output:
231 232 //
232 233 // http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
233 234 //
234 235 // The clear_output_callback will be passed a content object that contains
235 236 // stdout, stderr and other fields that are booleans, as well as the metadata object.
236 237 //
237 238 // The set_next_input_callback will be passed the text that should become the next
238 239 // input cell.
239 240
240 241 var content = {
241 242 code : code,
242 243 silent : true,
243 244 user_variables : [],
244 245 user_expressions : {},
245 246 allow_stdin : false
246 247 };
247 248 $.extend(true, content, options)
248 249 var msg = this._get_msg("execute_request", content);
249 250 this.shell_channel.send(JSON.stringify(msg));
250 251 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
251 252 return msg.header.msg_id;
252 253 };
253 254
254 255
255 256 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
256 257 // When calling this method pass a callbacks structure of the form:
257 258 //
258 259 // callbacks = {
259 260 // 'complete_reply': complete_reply_callback
260 261 // }
261 262 //
262 263 // The complete_reply_callback will be passed the content object of the
263 264 // complete_reply message documented here:
264 265 //
265 266 // http://ipython.org/ipython-doc/dev/development/messaging.html#complete
266 267 callbacks = callbacks || {};
267 268 var content = {
268 269 text : '',
269 270 line : line,
270 271 cursor_pos : cursor_pos
271 272 };
272 273 var msg = this._get_msg("complete_request", content);
273 274 this.shell_channel.send(JSON.stringify(msg));
274 275 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
275 276 return msg.header.msg_id;
276 277 };
277 278
278 279
279 280 Kernel.prototype.interrupt = function () {
280 281 if (this.running) {
281 282 $([IPython.events]).trigger('status_interrupting.Kernel');
282 283 $.post(this.kernel_url + "/interrupt");
283 284 };
284 285 };
285 286
286 287
287 288 Kernel.prototype.kill = function () {
288 289 if (this.running) {
289 290 this.running = false;
290 291 var settings = {
291 292 cache : false,
292 293 type : "DELETE"
293 294 };
294 295 $.ajax(this.kernel_url, settings);
295 296 };
296 297 };
297 298
298 299
299 300 // Reply handlers.
300 301
301 302 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
302 303 var callbacks = this._msg_callbacks[msg_id];
303 304 return callbacks;
304 305 };
305 306
306 307
307 308 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
308 309 this._msg_callbacks[msg_id] = callbacks || {};
309 310 }
310 311
311 312
312 313 Kernel.prototype._handle_shell_reply = function (e) {
313 314 reply = $.parseJSON(e.data);
314 315 var header = reply.header;
315 316 var content = reply.content;
316 317 var metadata = reply.metadata;
317 318 var msg_type = header.msg_type;
318 319 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
319 320 if (callbacks !== undefined) {
320 321 var cb = callbacks[msg_type];
321 322 if (cb !== undefined) {
322 323 cb(content, metadata);
323 324 }
324 325 };
325 326
326 327 if (content.payload !== undefined) {
327 328 var payload = content.payload || [];
328 329 this._handle_payload(callbacks, payload);
329 330 }
330 331 };
331 332
332 333
333 334 Kernel.prototype._handle_payload = function (callbacks, payload) {
334 335 var l = payload.length;
335 336 // Payloads are handled by triggering events because we don't want the Kernel
336 337 // to depend on the Notebook or Pager classes.
337 338 for (var i=0; i<l; i++) {
338 339 if (payload[i].source === 'IPython.zmq.page.page') {
339 340 var data = {'text':payload[i].text}
340 341 $([IPython.events]).trigger('open_with_text.Pager', data);
341 342 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
342 343 if (callbacks.set_next_input !== undefined) {
343 344 callbacks.set_next_input(payload[i].text)
344 345 }
345 346 }
346 347 };
347 348 };
348 349
349 350
350 351 Kernel.prototype._handle_iopub_reply = function (e) {
351 352 var reply = $.parseJSON(e.data);
352 353 var content = reply.content;
353 354 var msg_type = reply.header.msg_type;
354 355 var metadata = reply.metadata;
355 356 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
356 357 if (msg_type !== 'status' && callbacks === undefined) {
357 358 // Message not from one of this notebook's cells and there are no
358 359 // callbacks to handle it.
359 360 return;
360 361 }
361 362 var output_types = ['stream','display_data','pyout','pyerr'];
362 363 if (output_types.indexOf(msg_type) >= 0) {
363 364 var cb = callbacks['output'];
364 365 if (cb !== undefined) {
365 366 cb(msg_type, content, metadata);
366 367 }
367 368 } else if (msg_type === 'status') {
368 369 if (content.execution_state === 'busy') {
369 370 $([IPython.events]).trigger('status_busy.Kernel');
370 371 } else if (content.execution_state === 'idle') {
371 372 $([IPython.events]).trigger('status_idle.Kernel');
372 373 } else if (content.execution_state === 'dead') {
373 374 this.stop_channels();
374 375 $([IPython.events]).trigger('status_dead.Kernel');
375 376 };
376 377 } else if (msg_type === 'clear_output') {
377 378 var cb = callbacks['clear_output'];
378 379 if (cb !== undefined) {
379 380 cb(content, metadata);
380 381 }
381 382 };
382 383 };
383 384
384 385
385 386 IPython.Kernel = Kernel;
386 387
387 388 return IPython;
388 389
389 390 }(IPython));
390 391
General Comments 0
You need to be logged in to leave comments. Login now