Show More
@@ -29,6 +29,40 b' define([' | |||||
29 | */ |
|
29 | */ | |
30 |
|
30 | |||
31 | /** |
|
31 | /** | |
|
32 | * Load a notebook. | |||
|
33 | * | |||
|
34 | * Calls success_callback with notebook JSON object (as string), or | |||
|
35 | * error_callback with error. | |||
|
36 | * | |||
|
37 | * @method load_notebook | |||
|
38 | * @param {String} path | |||
|
39 | * @param {String} name | |||
|
40 | * @param {Function} success_callback | |||
|
41 | * @param {Function} error_callback | |||
|
42 | */ | |||
|
43 | ContentManager.prototype.load_notebook = function (path, name, success_callback, | |||
|
44 | error_callback) { | |||
|
45 | // We do the call with settings so we can set cache to false. | |||
|
46 | var settings = { | |||
|
47 | processData : false, | |||
|
48 | cache : false, | |||
|
49 | type : "GET", | |||
|
50 | dataType : "json", | |||
|
51 | success : success_callback, | |||
|
52 | error : error_callback, | |||
|
53 | }; | |||
|
54 | this.events.trigger('notebook_loading.Notebook'); | |||
|
55 | var url = utils.url_join_encode( | |||
|
56 | this.base_url, | |||
|
57 | 'api/notebooks', | |||
|
58 | path, | |||
|
59 | name | |||
|
60 | ); | |||
|
61 | $.ajax(url, settings); | |||
|
62 | }; | |||
|
63 | ||||
|
64 | ||||
|
65 | /** | |||
32 | * Creates a new notebook file at the specified path, and |
|
66 | * Creates a new notebook file at the specified path, and | |
33 | * opens that notebook in a new window. |
|
67 | * opens that notebook in a new window. | |
34 | * |
|
68 | * |
@@ -45,11 +45,40 b' define([' | |||||
45 | }; |
|
45 | }; | |
46 |
|
46 | |||
47 | /** |
|
47 | /** | |
|
48 | * Name of newly created notebook files. | |||
|
49 | * @type {string} | |||
|
50 | */ | |||
|
51 | ContentManager.NEW_NOTEBOOK_TITLE = 'Untitled'; | |||
|
52 | ||||
|
53 | /** | |||
|
54 | * Extension for notebook files. | |||
|
55 | * @type {string} | |||
|
56 | */ | |||
|
57 | ContentManager.NOTEBOOK_EXTENSION = 'ipynb'; | |||
|
58 | ||||
|
59 | ||||
|
60 | ContentManager.MULTIPART_BOUNDARY = '-------314159265358979323846'; | |||
|
61 | ||||
|
62 | ContentManager.NOTEBOOK_MIMETYPE = 'application/ipynb'; | |||
|
63 | ||||
|
64 | ||||
|
65 | /** | |||
48 | * low level Google Drive functions |
|
66 | * low level Google Drive functions | |
|
67 | * | |||
|
68 | * NOTE: these functions should only be called after gapi_ready has been | |||
|
69 | * resolved, with the excpetion of authorize(), and on_gapi_load() which | |||
|
70 | * is private and should not be called at all. Typical usage is: | |||
|
71 | * | |||
|
72 | * var that = this; | |||
|
73 | * this.gapi_ready.done(function () { | |||
|
74 | * that.get_id_for_path(...) | |||
|
75 | * ... | |||
|
76 | * }); | |||
49 | */ |
|
77 | */ | |
50 |
|
78 | |||
51 | /* |
|
79 | /* | |
52 | * Load Google Drive client library |
|
80 | * Load Google Drive client library | |
|
81 | * @private | |||
53 | * @method on_gapi_load |
|
82 | * @method on_gapi_load | |
54 | */ |
|
83 | */ | |
55 | ContentManager.prototype.on_gapi_load = function() { |
|
84 | ContentManager.prototype.on_gapi_load = function() { | |
@@ -111,6 +140,10 b' define([' | |||||
111 | * Gets the Google Drive folder ID corresponding to a path. Since |
|
140 | * Gets the Google Drive folder ID corresponding to a path. Since | |
112 | * the Google Drive API doesn't expose a path structure, it is necessary |
|
141 | * the Google Drive API doesn't expose a path structure, it is necessary | |
113 | * to manually walk the path from root. |
|
142 | * to manually walk the path from root. | |
|
143 | * @method get_id_for_path | |||
|
144 | * @param {String} path The path | |||
|
145 | * @param {Function} onSuccess called with the folder Id on success | |||
|
146 | * @param {Function} onFailure called with the error on Failure | |||
114 | */ |
|
147 | */ | |
115 | ContentManager.prototype.get_id_for_path = function(path, onSuccess, onFailure) { |
|
148 | ContentManager.prototype.get_id_for_path = function(path, onSuccess, onFailure) { | |
116 | // Use recursive strategy, with helper function |
|
149 | // Use recursive strategy, with helper function | |
@@ -134,7 +167,8 b' define([' | |||||
134 | } |
|
167 | } | |
135 |
|
168 | |||
136 | var query = ('mimeType = \'' + FOLDER_MIME_TYPE + '\'' |
|
169 | var query = ('mimeType = \'' + FOLDER_MIME_TYPE + '\'' | |
137 |
+ ' and title = \'' + this_component + '\'' |
|
170 | + ' and title = \'' + this_component + '\'' | |
|
171 | + ' and trashed = false'); | |||
138 | var request = gapi.client.drive.children.list({ |
|
172 | var request = gapi.client.drive.children.list({ | |
139 | 'folderId': base_id, |
|
173 | 'folderId': base_id, | |
140 | 'q': query |
|
174 | 'q': query | |
@@ -165,12 +199,219 b' define([' | |||||
165 | get_id_for_relative_path('root', path.split('/').reverse()); |
|
199 | get_id_for_relative_path('root', path.split('/').reverse()); | |
166 | } |
|
200 | } | |
167 |
|
201 | |||
|
202 | /** | |||
|
203 | * Gets the Google Drive folder ID corresponding to a path. Since | |||
|
204 | * the Google Drive API doesn't expose a path structure, it is necessary | |||
|
205 | * to manually walk the path from root. | |||
|
206 | * @method get_id_for_path | |||
|
207 | * @param {String} folder_id The google Drive folder id to search | |||
|
208 | * @param {String} filename The filename to find in folder_id | |||
|
209 | * @param {Function} onSuccess called with a files resource on success (see | |||
|
210 | * Google Drive API documentation for more information on the files | |||
|
211 | * resource). | |||
|
212 | * @param {Function} onFailure called with the error on Failure | |||
|
213 | */ | |||
|
214 | ContentManager.prototype.get_resource_for_filename = function( | |||
|
215 | folder_id, | |||
|
216 | filename, | |||
|
217 | onSuccess, | |||
|
218 | onFailure) { | |||
|
219 | var query = ('title = \'' + filename + '\'' | |||
|
220 | + ' and \'' + folder_id + '\' in parents' | |||
|
221 | + ' and trashed = false'); | |||
|
222 | var request = gapi.client.drive.files.list({ | |||
|
223 | 'q': query | |||
|
224 | }); | |||
|
225 | request.execute(function(response) { | |||
|
226 | if (!response || response['error']) { | |||
|
227 | onFailure(response ? response['error'] : null); | |||
|
228 | return; | |||
|
229 | } | |||
|
230 | ||||
|
231 | var files = response['items']; | |||
|
232 | if (!files) { | |||
|
233 | // 'directory does not exist' error. | |||
|
234 | onFailure(); | |||
|
235 | return; | |||
|
236 | } | |||
|
237 | ||||
|
238 | if (files.length > 1) { | |||
|
239 | // 'runtime error' this should not happen | |||
|
240 | onFailure(); | |||
|
241 | return; | |||
|
242 | } | |||
|
243 | ||||
|
244 | onSuccess(files[0]); | |||
|
245 | ||||
|
246 | }); | |||
|
247 | }; | |||
|
248 | ||||
|
249 | /** | |||
|
250 | * Uploads a notebook to Drive, either creating a new one or saving an | |||
|
251 | * existing one. | |||
|
252 | * | |||
|
253 | * @method upload_to_drive | |||
|
254 | * @param {string} data The file contents as a string | |||
|
255 | * @param {Object} metadata File metadata | |||
|
256 | * @param {function(gapi.client.drive.files.Resource)} success_callback callback for | |||
|
257 | * success | |||
|
258 | * @param {function(?):?} error_callback callback for error, takes response object | |||
|
259 | * @param {string=} opt_fileId file Id. If false, a new file is created. | |||
|
260 | * @param {Object?} opt_params a dictionary containing the following keys | |||
|
261 | * pinned: whether this save should be pinned | |||
|
262 | */ | |||
|
263 | ContentManager.prototype.upload_to_drive = function(data, metadata, | |||
|
264 | success_callback, error_callback, opt_fileId, opt_params) { | |||
|
265 | var params = opt_params || {}; | |||
|
266 | var delimiter = '\r\n--' + ContentManager.MULTIPART_BOUNDARY + '\r\n'; | |||
|
267 | var close_delim = '\r\n--' + ContentManager.MULTIPART_BOUNDARY + '--'; | |||
|
268 | var body = delimiter + | |||
|
269 | 'Content-Type: application/json\r\n\r\n' + | |||
|
270 | JSON.stringify(metadata) + | |||
|
271 | delimiter + | |||
|
272 | 'Content-Type: ' + ContentManager.NOTEBOOK_MIMETYPE + '\r\n' + | |||
|
273 | '\r\n' + | |||
|
274 | data + | |||
|
275 | close_delim; | |||
|
276 | ||||
|
277 | var path = '/upload/drive/v2/files'; | |||
|
278 | var method = 'POST'; | |||
|
279 | if (opt_fileId) { | |||
|
280 | path += '/' + opt_fileId; | |||
|
281 | method = 'PUT'; | |||
|
282 | } | |||
|
283 | ||||
|
284 | var request = gapi.client.request({ | |||
|
285 | 'path': path, | |||
|
286 | 'method': method, | |||
|
287 | 'params': { | |||
|
288 | 'uploadType': 'multipart', | |||
|
289 | 'pinned' : params['pinned'] | |||
|
290 | }, | |||
|
291 | 'headers': { | |||
|
292 | 'Content-Type': 'multipart/mixed; boundary="' + | |||
|
293 | ContentManager.MULTIPART_BOUNDARY + '"' | |||
|
294 | }, | |||
|
295 | 'body': body | |||
|
296 | }); | |||
|
297 | request.execute(function(response) { | |||
|
298 | if (!response || response['error']) { | |||
|
299 | error_callback(response ? response['error'] : null); | |||
|
300 | return; | |||
|
301 | } | |||
|
302 | ||||
|
303 | success_callback(response); | |||
|
304 | }); | |||
|
305 | }; | |||
|
306 | ||||
|
307 | /** | |||
|
308 | * Obtains the filename that should be used for a new file in a given folder. | |||
|
309 | * This is the next file in the series Untitled0, Untitled1, ... in the given | |||
|
310 | * drive folder. As a fallback, returns Untitled. | |||
|
311 | * | |||
|
312 | * @method get_new_filename | |||
|
313 | * @param {function(string)} callback Called with the name for the new file. | |||
|
314 | * @param {string} opt_folderId optinal Drive folder Id to search for | |||
|
315 | * filenames. Uses root, if none is specified. | |||
|
316 | */ | |||
|
317 | ContentManager.prototype.get_new_filename = function(callback, opt_folderId) { | |||
|
318 | /** @type {string} */ | |||
|
319 | var folderId = opt_folderId || 'root'; | |||
|
320 | var query = 'title contains \'' + ContentManager.NEW_NOTEBOOK_TITLE + '\'' + | |||
|
321 | ' and \'' + folderId + '\' in parents' + | |||
|
322 | ' and trashed = false'; | |||
|
323 | var request = gapi.client.drive.files.list({ | |||
|
324 | 'maxResults': 1000, | |||
|
325 | 'folderId' : folderId, | |||
|
326 | 'q': query | |||
|
327 | }); | |||
|
328 | ||||
|
329 | request.execute(function(response) { | |||
|
330 | // Use 'Untitled.ipynb' as a fallback in case of error | |||
|
331 | var fallbackFilename = ContentManager.NEW_NOTEBOOK_TITLE + '.' + | |||
|
332 | ContentManager.NOTEBOOK_EXTENSION; | |||
|
333 | if (!response || response['error']) { | |||
|
334 | callback(fallbackFilename); | |||
|
335 | return; | |||
|
336 | } | |||
|
337 | ||||
|
338 | var files = response['items'] || []; | |||
|
339 | var existingFilenames = $.map(files, function(filesResource) { | |||
|
340 | return filesResource['title']; | |||
|
341 | }); | |||
|
342 | ||||
|
343 | // Loop over file names Untitled0, ... , UntitledN where N is the number of | |||
|
344 | // elements in existingFilenames. Select the first file name that does not | |||
|
345 | // belong to existingFilenames. This is guaranteed to find a file name | |||
|
346 | // that does not belong to existingFilenames, since there are N + 1 file | |||
|
347 | // names tried, and existingFilenames contains N elements. | |||
|
348 | for (var i = 0; i <= existingFilenames.length; i++) { | |||
|
349 | /** @type {string} */ | |||
|
350 | var filename = ContentManager.NEW_NOTEBOOK_TITLE + i + '.' + | |||
|
351 | ContentManager.NOTEBOOK_EXTENSION; | |||
|
352 | if (existingFilenames.indexOf(filename) == -1) { | |||
|
353 | callback(filename); | |||
|
354 | return; | |||
|
355 | } | |||
|
356 | } | |||
|
357 | ||||
|
358 | // Control should not reach this point, so an error has occured | |||
|
359 | callback(fallbackFilename); | |||
|
360 | }); | |||
|
361 | }; | |||
168 |
|
362 | |||
169 | /** |
|
363 | /** | |
170 | * Notebook Functions |
|
364 | * Notebook Functions | |
171 | */ |
|
365 | */ | |
172 |
|
366 | |||
173 | /** |
|
367 | /** | |
|
368 | * Load a notebook. | |||
|
369 | * | |||
|
370 | * Calls success_callback with notebook JSON object (as string), or | |||
|
371 | * error_callback with error. | |||
|
372 | * | |||
|
373 | * @method load_notebook | |||
|
374 | * @param {String} path | |||
|
375 | * @param {String} name | |||
|
376 | * @param {Function} success_callback | |||
|
377 | * @param {Function} error_callback | |||
|
378 | */ | |||
|
379 | ContentManager.prototype.load_notebook = function (path, name, success_callback, | |||
|
380 | error_callback) { | |||
|
381 | var that = this; | |||
|
382 | this.gapi_ready.done(function() { | |||
|
383 | that.get_id_for_path(path, function(folder_id) { | |||
|
384 | that.get_resource_for_filename(folder_id, name, function(file_resource) { | |||
|
385 | // Sends request to load file to drive. | |||
|
386 | var token = gapi.auth.getToken()['access_token']; | |||
|
387 | var xhrRequest = new XMLHttpRequest(); | |||
|
388 | xhrRequest.open('GET', file_resource['downloadUrl'], true); | |||
|
389 | xhrRequest.setRequestHeader('Authorization', 'Bearer ' + token); | |||
|
390 | xhrRequest.onreadystatechange = function(e) { | |||
|
391 | if (xhrRequest.readyState == 4) { | |||
|
392 | if (xhrRequest.status == 200) { | |||
|
393 | var notebook_contents = xhrRequest.responseText; | |||
|
394 | //colab.nbformat.convertJsonNotebookToRealtime( | |||
|
395 | // notebook_contents, model); | |||
|
396 | var model = JSON.parse(notebook_contents); | |||
|
397 | ||||
|
398 | success_callback({ | |||
|
399 | content: model, | |||
|
400 | // A hack to deal with file/memory format conversions | |||
|
401 | name: model.metadata.name | |||
|
402 | }); | |||
|
403 | } else { | |||
|
404 | error_callback(xhrRequest); | |||
|
405 | } | |||
|
406 | } | |||
|
407 | }; | |||
|
408 | xhrRequest.send(); | |||
|
409 | }, error_callback) | |||
|
410 | }, error_callback); | |||
|
411 | }); | |||
|
412 | }; | |||
|
413 | ||||
|
414 | /** | |||
174 | * Creates a new notebook file at the specified path, and |
|
415 | * Creates a new notebook file at the specified path, and | |
175 | * opens that notebook in a new window. |
|
416 | * opens that notebook in a new window. | |
176 | * |
|
417 | * | |
@@ -178,46 +419,46 b' define([' | |||||
178 | * @param {String} path The path to create the new notebook at |
|
419 | * @param {String} path The path to create the new notebook at | |
179 | */ |
|
420 | */ | |
180 | ContentManager.prototype.new_notebook = function(path) { |
|
421 | ContentManager.prototype.new_notebook = function(path) { | |
181 |
var |
|
422 | var that = this; | |
182 | var settings = { |
|
423 | this.gapi_ready.done(function() { | |
183 | processData : false, |
|
424 | that.get_id_for_path(path, function(folder_id) { | |
184 | cache : false, |
|
425 | that.get_new_filename(function(filename) { | |
185 | type : "POST", |
|
426 | var data = { | |
186 | dataType : "json", |
|
427 | 'worksheets': [{ | |
187 | async : false, |
|
428 | 'cells' : [{ | |
188 | success : function (data, status, xhr){ |
|
429 | 'cell_type': 'code', | |
|
430 | 'input': '', | |||
|
431 | 'outputs': [], | |||
|
432 | 'language': 'python' | |||
|
433 | }], | |||
|
434 | }], | |||
|
435 | 'metadata': { | |||
|
436 | 'name': filename, | |||
|
437 | }, | |||
|
438 | 'nbformat': 3, | |||
|
439 | 'nbformat_minor': 0 | |||
|
440 | }; | |||
|
441 | var metadata = { | |||
|
442 | 'parents' : [{'id' : folder_id}], | |||
|
443 | 'title' : filename, | |||
|
444 | 'description': 'IP[y] file', | |||
|
445 | 'mimeType': ContentManager.NOTEBOOK_MIMETYPE | |||
|
446 | } | |||
|
447 | that.upload_to_drive(JSON.stringify(data), metadata, function (data, status, xhr) { | |||
189 | var notebook_name = data.name; |
|
448 | var notebook_name = data.name; | |
190 | window.open( |
|
449 | window.open( | |
191 | utils.url_join_encode( |
|
450 | utils.url_join_encode( | |
192 | base_url, |
|
451 | that.base_url, | |
193 | 'notebooks', |
|
452 | 'notebooks', | |
194 | path, |
|
453 | path, | |
195 |
|
|
454 | filename | |
196 | ), |
|
455 | ), | |
197 | '_blank' |
|
456 | '_blank' | |
198 | ); |
|
457 | ); | |
199 | }, |
|
458 | }, function(){}); | |
200 | error : function(xhr, status, error) { |
|
459 | }, folder_id); | |
201 | utils.log_ajax_error(xhr, status, error); |
|
460 | }) | |
202 | var msg; |
|
|||
203 | if (xhr.responseJSON && xhr.responseJSON.message) { |
|
|||
204 | msg = xhr.responseJSON.message; |
|
|||
205 | } else { |
|
|||
206 | msg = xhr.statusText; |
|
|||
207 | } |
|
|||
208 | dialog.modal({ |
|
|||
209 | title : 'Creating Notebook Failed', |
|
|||
210 | body : "The error was: " + msg, |
|
|||
211 | buttons : {'OK' : {'class' : 'btn-primary'}} |
|
|||
212 |
|
|
461 | }); | |
213 | } |
|
|||
214 | }; |
|
|||
215 | var url = utils.url_join_encode( |
|
|||
216 | base_url, |
|
|||
217 | 'api/notebooks', |
|
|||
218 | path |
|
|||
219 | ); |
|
|||
220 | $.ajax(url,settings); |
|
|||
221 | }; |
|
462 | }; | |
222 |
|
463 | |||
223 | ContentManager.prototype.delete_notebook = function(name, path) { |
|
464 | ContentManager.prototype.delete_notebook = function(name, path) { | |
@@ -380,7 +621,8 b' define([' | |||||
380 | that.get_id_for_path(path, function(folder_id) { |
|
621 | that.get_id_for_path(path, function(folder_id) { | |
381 | query = ('(fileExtension = \'ipynb\' or' |
|
622 | query = ('(fileExtension = \'ipynb\' or' | |
382 | + ' mimeType = \'' + FOLDER_MIME_TYPE + '\')' |
|
623 | + ' mimeType = \'' + FOLDER_MIME_TYPE + '\')' | |
383 |
+ ' and \'' + folder_id + '\' in parents' |
|
624 | + ' and \'' + folder_id + '\' in parents' | |
|
625 | + ' and trashed = false'); | |||
384 | var request = gapi.client.drive.files.list({ |
|
626 | var request = gapi.client.drive.files.list({ | |
385 | 'maxResults' : 1000, |
|
627 | 'maxResults' : 1000, | |
386 | 'q' : query |
|
628 | 'q' : query |
@@ -2140,26 +2140,13 b' define([' | |||||
2140 | * @param {String} notebook_name and path A notebook to load |
|
2140 | * @param {String} notebook_name and path A notebook to load | |
2141 | */ |
|
2141 | */ | |
2142 | Notebook.prototype.load_notebook = function (notebook_name, notebook_path) { |
|
2142 | Notebook.prototype.load_notebook = function (notebook_name, notebook_path) { | |
2143 | var that = this; |
|
|||
2144 | this.notebook_name = notebook_name; |
|
2143 | this.notebook_name = notebook_name; | |
2145 | this.notebook_path = notebook_path; |
|
2144 | this.notebook_path = notebook_path; | |
2146 | // We do the call with settings so we can set cache to false. |
|
2145 | this.content_manager.load_notebook( | |
2147 | var settings = { |
|
2146 | notebook_path, | |
2148 | processData : false, |
|
2147 | notebook_name, | |
2149 | cache : false, |
|
2148 | $.proxy(this.load_notebook_success,this), | |
2150 | type : "GET", |
|
2149 | $.proxy(this.load_notebook_error,this)); | |
2151 | dataType : "json", |
|
|||
2152 | success : $.proxy(this.load_notebook_success,this), |
|
|||
2153 | error : $.proxy(this.load_notebook_error,this), |
|
|||
2154 | }; |
|
|||
2155 | this.events.trigger('notebook_loading.Notebook'); |
|
|||
2156 | var url = utils.url_join_encode( |
|
|||
2157 | this.base_url, |
|
|||
2158 | 'api/contents', |
|
|||
2159 | this.notebook_path, |
|
|||
2160 | this.notebook_name |
|
|||
2161 | ); |
|
|||
2162 | $.ajax(url, settings); |
|
|||
2163 | }; |
|
2150 | }; | |
2164 |
|
2151 | |||
2165 | /** |
|
2152 | /** |
General Comments 0
You need to be logged in to leave comments.
Login now