From 757bf961c637f11de5167e58b116f645a3c042e4 2013-05-02 00:32:42 From: MinRK Date: 2013-05-02 00:32:42 Subject: [PATCH] add autosave timer autosave interval is tuned based on the duration of saves. Autosave will never happen more frequently than every 30 seconds, and if saves take more than 3 seconds, autosave will fire every 10x the duration of the save (i.e. if save takes 6 seconds, it will be every 60 seconds, etc.) --- diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index 22f56e2..268cd0f 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -41,6 +41,9 @@ var IPython = (function (IPython) { this.metadata = {}; this._checkpoint_after_save = false; this.last_checkpoint = null; + this.autosave_interval = 0; + this.autosave_timer = null; + this.minimum_autosave_interval = 30000; // single worksheet for now this.worksheet_metadata = {}; this.control_key_active = false; @@ -1552,6 +1555,31 @@ var IPython = (function (IPython) { }; /** + * Start an autosave timer, for periodically saving the notebook. + * + * @method autosave_notebook + * @param {Integer} interval the autosave interval in milliseconds + */ + Notebook.prototype.autosave_notebook = function (interval) { + var that = this; + // clear previous interval, so we don't get simultaneous timers + if (this.autosave_timer) { + clearInterval(this.autosave_timer); + } + + this.autosave_interval = interval; + if (interval) { + this.autosave_timer = setInterval(function() { + that.save_notebook(); + }, interval); + $([IPython.events]).trigger("autosave_enabled.Notebook", interval); + } else { + this.autosave_timer = null; + $([IPython.events]).trigger("autosave_disabled.Notebook"); + }; + }; + + /** * Save this notebook on the server. * * @method save_notebook @@ -1562,6 +1590,10 @@ var IPython = (function (IPython) { data.metadata.name = this.notebook_name; data.nbformat = this.nbformat; data.nbformat_minor = this.nbformat_minor; + + // time the ajax call for autosave tuning purposes. + var start = new Date().getTime(); + // We do the call with settings so we can set cache to false. var settings = { processData : false, @@ -1569,8 +1601,8 @@ var IPython = (function (IPython) { type : "PUT", data : JSON.stringify(data), headers : {'Content-Type': 'application/json'}, - success : $.proxy(this.save_notebook_success,this), - error : $.proxy(this.save_notebook_error,this) + success : $.proxy(this.save_notebook_success, this, start), + error : $.proxy(this.save_notebook_error, this) }; $([IPython.events]).trigger('notebook_saving.Notebook'); var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id; @@ -1581,13 +1613,15 @@ var IPython = (function (IPython) { * Success callback for saving a notebook. * * @method save_notebook_success + * @param {Integer} start the time when the save request started * @param {Object} data JSON representation of a notebook * @param {String} status Description of response status * @param {jqXHR} xhr jQuery Ajax object */ - Notebook.prototype.save_notebook_success = function (data, status, xhr) { + Notebook.prototype.save_notebook_success = function (start, data, status, xhr) { this.dirty = false; $([IPython.events]).trigger('notebook_saved.Notebook'); + this._update_autosave_interval(start); if (this._checkpoint_after_save) { this.create_checkpoint(); this._checkpoint_after_save = false; @@ -1595,6 +1629,26 @@ var IPython = (function (IPython) { }; /** + * update the autosave interval based on how long the last save took + * + * @method _update_autosave_interval + * @param {Integer} timestamp when the save request started + */ + Notebook.prototype._update_autosave_interval = function (start) { + var duration = (new Date().getTime() - start); + if (this.autosave_interval) { + // new save interval: higher of 10x save duration or parameter (default 30 seconds) + var interval = Math.max(10 * duration, this.minimum_autosave_interval); + // round to 10 seconds, otherwise we will be setting a new interval too often + interval = 10000 * Math.round(interval / 10000); + // set new interval, if it's changed + if (interval != this.autosave_interval) { + this.autosave_notebook(interval); + } + } + }; + + /** * Failure callback for saving a notebook. * * @method save_notebook_error diff --git a/IPython/frontend/html/notebook/static/js/notebookmain.js b/IPython/frontend/html/notebook/static/js/notebookmain.js index 64ffd95..319f8cb 100644 --- a/IPython/frontend/html/notebook/static/js/notebookmain.js +++ b/IPython/frontend/html/notebook/static/js/notebookmain.js @@ -80,14 +80,19 @@ $(document).ready(function () { IPython.page.show(); IPython.layout_manager.do_resize(); - $([IPython.events]).on('notebook_loaded.Notebook', function () { + var first_load = function () { IPython.layout_manager.do_resize(); var hash = document.location.hash; if (hash) { document.location.hash = ''; document.location.hash = hash; } - }); + IPython.notebook.autosave_notebook(30000); + // only do this once + $([IPython.events]).off('notebook_loaded.Notebook', first_load); + }; + + $([IPython.events]).on('notebook_loaded.Notebook', first_load); IPython.notebook.load_notebook($('body').data('notebookId')); }); diff --git a/IPython/frontend/html/notebook/static/js/notificationarea.js b/IPython/frontend/html/notebook/static/js/notificationarea.js index 874ea1d..81a18ee 100644 --- a/IPython/frontend/html/notebook/static/js/notificationarea.js +++ b/IPython/frontend/html/notebook/static/js/notificationarea.js @@ -203,6 +203,14 @@ var IPython = (function (IPython) { nnw.set_message("Checkpoint restore failed"); }); + // Autosave events + $([IPython.events]).on('autosave_disabled.Notebook', function () { + nnw.set_message("Autosave disabled", 2000); + }); + $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) { + nnw.set_message("Saving every " + interval / 1000 + "s", 1000); + }); + }; IPython.NotificationArea = NotificationArea;