From bb95f71f5e08975ee98192616d1aad76a5471c1d Mon Sep 17 00:00:00 2001 From: John Miller Date: Wed, 19 Oct 2016 14:43:19 -0500 Subject: [PATCH] Give ein notebooks content checkpoint functionality. --- lisp/ein-contents-api.el | 24 ++++++------ lisp/ein-notebook.el | 83 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/lisp/ein-contents-api.el b/lisp/ein-contents-api.el index 251e544..64df424 100644 --- a/lisp/ein-contents-api.el +++ b/lisp/ein-contents-api.el @@ -54,40 +54,40 @@ global setting. For global setting and more information, see `ein:$content-url-or-port' URL or port of Jupyter server. -`ein:$content-name +`ein:$content-name' The name/filename of the content. Always equivalent to the last part of the path field -`ein:$content-path +`ein:$content-path' The full file path. It will not start with /, and it will be /-delimited. -`ein:$content-type +`ein:$content-type' One of three values: :directory, :file, :notebook. -`ein:$content-writable +`ein:$content-writable' Indicates if requester has permission to modified the requested content. -`ein:$content-created +`ein:$content-created' -`ein:$content-last-modified +`ein:$content-last-modified' -`ein:$content-mimetype +`ein:$content-mimetype' Specify the mime-type of :file content, null otherwise. -`ein:$content-raw-content +`ein:$content-raw-content' Contents of resource as returned by Jupyter. Depending on content-type will hold: :directory : JSON list of models for each item in the directory. :file : Text of file as a string or base64 encoded string if mimetype is other than 'text/plain'. :notebook : JSON structure of the file. -`ein:$content-format +`ein:$content-format' Value will depend on content-type: :directory : :json. :file : Either :text or :base64 :notebook : :json. -`ein:$content-checkpoints +`ein:$content-checkpoints' Names auto-saved checkpoints for content. Stored as a list of ( . ) pairs. " @@ -439,9 +439,11 @@ global setting. For global setting and more information, see :error (apply-partially #'ein:content-query-checkpoints-error content)))) (defun* ein:content-query-checkpoints-success (content cb cbargs &key data status response &allow-other-keys) + (unless (listp (car data)) + (setq data (list data))) (setf (ein:$content-checkpoints content) data) (when cb - (apply cb cbargs))) + (apply cb content cbargs))) (defun* ein:content-query-checkpoints-error (content &key symbol-status response &allow-other-keys) (ein:log 'error "Content checkpoint operation failed with status %s (%s)." symbol-status response)) diff --git a/lisp/ein-notebook.el b/lisp/ein-notebook.el index a2da160..a4c66d9 100644 --- a/lisp/ein-notebook.el +++ b/lisp/ein-notebook.el @@ -62,6 +62,12 @@ (make-obsolete-variable 'ein:notebook-discard-output-on-save nil "0.2.0") +(defcustom ein:notebook-checkpoint-frequency 60 + "Controls frequency (in seconds) at which checkpoints are +generated for the current notebook." + :type 'number + :group 'ein) + (defcustom ein:notebook-discard-output-on-save 'no "Configure if the output part of the cell should be saved or not. @@ -206,6 +212,10 @@ Current buffer for these functions is set to the notebook buffer.") `ein:$notebook-api-version' : integer Major version of the IPython notebook server we are talking to. + +`ein:$notebook-checkpoints' + Names auto-saved checkpoints for content. Stored as a list + of ( . ) pairs. " url-or-port notebook-id ;; In IPython-2.0 this is "[:path]/[:name].ipynb" @@ -223,7 +233,8 @@ Current buffer for these functions is set to the notebook buffer.") worksheets scratchsheets api-version - ) + checkpoint-timer + checkpoints) (ein:deflocal ein:%notebook% nil "Buffer local variable to store an instance of `ein:$notebook'.") @@ -396,9 +407,11 @@ See `ein:notebook-open' for more information." (cons #'ein:notebook-buffer-list notebook))) (ein:notebook-put-opened-notebook notebook) (ein:notebook--check-nbformat (ein:$content-raw-content content)) + (ein:notebook-enable-checkpoints notebook) (ein:log 'info "Notebook %s is ready" (ein:$notebook-notebook-name notebook)))) + (defun ein:notebook--different-number (n1 n2) (and (numberp n1) (numberp n2) (not (= n1 n2)))) @@ -429,6 +442,34 @@ of minor mode." ;;; Initialization. +(defun ein:notebook-enable-checkpoints (notebook) + "Enable checkpoints for notebook." + (interactive + (let* ((notebook (or (ein:get-notebook) + (completing-read + "Select notebook [URL-OR-PORT/NAME]: " + (ein:notebook-opened-buffer-names))))) + (list notebook))) + (if (stringp notebook) + (error "Fix me!")) ;; FIXME + (setf (ein:$notebook-checkpoint-timer notebook) + (run-at-time t ein:notebook-checkpoint-frequency #'ein:notebook-save-checkpoint notebook)) + (ein:log 'info "Enabling checkpoints for %s with frequency %s seconds." + (ein:$notebook-notebook-name notebook) + ein:notebook-checkpoint-frequency)) + +(defun ein:notebook-disable-checkpoints (notebook) + "Stop automatically saving checkpoints for notebook." + (interactive + (let* ((notebook (or (ein:get-notebook) + (completing-read + "Select notebook [URL-OR-PORT/NAME]: " + (ein:notebook-opened-buffer-names))))) + (list notebook))) + (ein:log 'info "Disabling auto checkpoints for notebook %s" (ein:$notebook-notebook-name notebook)) + (when (ein:$notebook-checkpoint-timer notebook) + (cancel-timer (ein:$notebook-checkpoint-timer notebook)))) + (defun ein:notebook-bind-events (notebook events) "Bind events related to PAGER to the event handler EVENTS." (setf (ein:$notebook-events notebook) events) @@ -525,8 +566,8 @@ notebook buffer then the user will be prompted to select an opened notebook." "Select notebook [URL-OR-PORT/NAME]: " (ein:notebook-opened-buffer-names)))) (kernel-name (completing-read - "Select kernel: " - (ein:list-available-kernels (ein:$notebook-url-or-port notebook))))) + "Select kernel: " + (ein:list-available-kernels (ein:$notebook-url-or-port notebook))))) (list notebook kernel-name))) (setf (ein:$notebook-kernelspec notebook) (ein:get-kernelspec (ein:$notebook-url-or-port notebook) kernel-name)) @@ -881,6 +922,42 @@ as usual." (ein:kernel-kill kernel #'ein:notebook-close (list ein:%notebook%)) (ein:notebook-close ein:%notebook%))))) +(defun ein:fast-content-from-notebook (notebook) + "Quickly generate a basic content structure from notebook. This +function does not generate the full json representation of the +notebook worksheets." + (make-ein:$content :name (ein:$notebook-notebook-name notebook) + :path (ein:$notebook-notebook-path notebook) + :url-or-port (ein:$notebook-url-or-port notebook) + :type "notebook" + :ipython-version (ein:$notebook-api-version notebook))) + +(defun ein:notebook-save-checkpoint (notebook) + (let ((content (ein:fast-content-from-notebook notebook))) + (ein:content-create-checkpoint content + #'(lambda (content) + (ein:log 'info "Checkpoint for %s generated." + (ein:$notebook-notebook-name notebook)) + (setf (ein:$notebook-checkpoints notebook) + (ein:$content-checkpoints content)))))) + +(defun ein:notebook-list-checkpoint-ids (notebook) + (loop for cp in (ein:$notebook-checkpoints notebook) + collecting (plist-get cp :id))) + +(defun ein:notebook-restore-to-checkpoint (notebook checkpoint) + (interactive + (let* ((notebook (ein:get-notebook)) + (checkpoint (completing-read + "Select checkpoint: " + (ein:notebook-list-checkpoint-ids notebook)))) + (list notebook checkpoint))) + (ein:content-restore-checkpoint (ein:fast-content-from-notebook notebook) + checkpoint) + (ein:notebook-close notebook) + (ein:notebook-open (ein:$notebook-url-or-port notebook) + (ein:$notebook-notebook-path notebook))) + ;;; Worksheet