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 | 66 | * Creates a new notebook file at the specified path, and |
|
33 | 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 | 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 | 80 | * Load Google Drive client library |
|
81 | * @private | |
|
53 | 82 | * @method on_gapi_load |
|
54 | 83 | */ |
|
55 | 84 | ContentManager.prototype.on_gapi_load = function() { |
@@ -111,6 +140,10 b' define([' | |||
|
111 | 140 | * Gets the Google Drive folder ID corresponding to a path. Since |
|
112 | 141 | * the Google Drive API doesn't expose a path structure, it is necessary |
|
113 | 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 | 148 | ContentManager.prototype.get_id_for_path = function(path, onSuccess, onFailure) { |
|
116 | 149 | // Use recursive strategy, with helper function |
@@ -134,7 +167,8 b' define([' | |||
|
134 | 167 | } |
|
135 | 168 | |
|
136 | 169 | var query = ('mimeType = \'' + FOLDER_MIME_TYPE + '\'' |
|
137 |
+ ' and title = \'' + this_component + '\'' |
|
|
170 | + ' and title = \'' + this_component + '\'' | |
|
171 | + ' and trashed = false'); | |
|
138 | 172 | var request = gapi.client.drive.children.list({ |
|
139 | 173 | 'folderId': base_id, |
|
140 | 174 | 'q': query |
@@ -165,12 +199,219 b' define([' | |||
|
165 | 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 | 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 | 415 | * Creates a new notebook file at the specified path, and |
|
175 | 416 | * opens that notebook in a new window. |
|
176 | 417 | * |
@@ -178,46 +419,46 b' define([' | |||
|
178 | 419 | * @param {String} path The path to create the new notebook at |
|
179 | 420 | */ |
|
180 | 421 | ContentManager.prototype.new_notebook = function(path) { |
|
181 |
var |
|
|
182 | var settings = { | |
|
183 | processData : false, | |
|
184 | cache : false, | |
|
185 | type : "POST", | |
|
186 | dataType : "json", | |
|
187 | async : false, | |
|
188 | success : function (data, status, xhr){ | |
|
422 | var that = this; | |
|
423 | this.gapi_ready.done(function() { | |
|
424 | that.get_id_for_path(path, function(folder_id) { | |
|
425 | that.get_new_filename(function(filename) { | |
|
426 | var data = { | |
|
427 | 'worksheets': [{ | |
|
428 | 'cells' : [{ | |
|
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 | 448 | var notebook_name = data.name; |
|
190 | 449 | window.open( |
|
191 | 450 | utils.url_join_encode( |
|
192 | base_url, | |
|
451 | that.base_url, | |
|
193 | 452 | 'notebooks', |
|
194 | 453 | path, |
|
195 |
|
|
|
454 | filename | |
|
196 | 455 | ), |
|
197 | 456 | '_blank' |
|
198 | 457 | ); |
|
199 | }, | |
|
200 | error : function(xhr, status, error) { | |
|
201 | utils.log_ajax_error(xhr, status, error); | |
|
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'}} | |
|
458 | }, function(){}); | |
|
459 | }, folder_id); | |
|
460 | }) | |
|
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 | 464 | ContentManager.prototype.delete_notebook = function(name, path) { |
@@ -380,7 +621,8 b' define([' | |||
|
380 | 621 | that.get_id_for_path(path, function(folder_id) { |
|
381 | 622 | query = ('(fileExtension = \'ipynb\' or' |
|
382 | 623 | + ' mimeType = \'' + FOLDER_MIME_TYPE + '\')' |
|
383 |
+ ' and \'' + folder_id + '\' in parents' |
|
|
624 | + ' and \'' + folder_id + '\' in parents' | |
|
625 | + ' and trashed = false'); | |
|
384 | 626 | var request = gapi.client.drive.files.list({ |
|
385 | 627 | 'maxResults' : 1000, |
|
386 | 628 | 'q' : query |
@@ -2140,26 +2140,13 b' define([' | |||
|
2140 | 2140 | * @param {String} notebook_name and path A notebook to load |
|
2141 | 2141 | */ |
|
2142 | 2142 | Notebook.prototype.load_notebook = function (notebook_name, notebook_path) { |
|
2143 | var that = this; | |
|
2144 | 2143 | this.notebook_name = notebook_name; |
|
2145 | 2144 | this.notebook_path = notebook_path; |
|
2146 | // We do the call with settings so we can set cache to false. | |
|
2147 | var settings = { | |
|
2148 | processData : false, | |
|
2149 | cache : false, | |
|
2150 | type : "GET", | |
|
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); | |
|
2145 | this.content_manager.load_notebook( | |
|
2146 | notebook_path, | |
|
2147 | notebook_name, | |
|
2148 | $.proxy(this.load_notebook_success,this), | |
|
2149 | $.proxy(this.load_notebook_error,this)); | |
|
2163 | 2150 | }; |
|
2164 | 2151 | |
|
2165 | 2152 | /** |
General Comments 0
You need to be logged in to leave comments.
Login now