Ver código fonte

wip implement autosave

Steven Silvester 8 anos atrás
pai
commit
e550ce8966
3 arquivos alterados com 249 adições e 1 exclusões
  1. 69 1
      src/docmanager/context.ts
  2. 145 0
      src/docmanager/savehandler.ts
  3. 35 0
      src/docregistry/interfaces.ts

+ 69 - 1
src/docmanager/context.ts

@@ -175,6 +175,34 @@ class Context implements IDocumentContext<IDocumentModel> {
     return this._manager.revert(this._id);
   }
 
+  /**
+   * Create a checkpoint for the file.
+   */
+  createCheckpoint(): Promise<IContents.ICheckpointModel> {
+    return this._manager.createCheckpoint(this._id);
+  }
+
+  /**
+   * Delete a checkpoint for the file.
+   */
+  deleteCheckpoint(checkpointID: string): Promise<void> {
+    return this._manager.deleteCheckpoint(this.id, checkpointID);
+  }
+
+  /**
+   * Restore the file to a known checkpoint state.
+   */
+  restoreCheckpoint(checkpointID?: string): Promise<void> {
+    return this._manager.restoreCheckpoint(this.id, checkpointID);
+  }
+
+  /**
+   * List available checkpoints for the file.
+   */
+  listCheckpoints(): Promise<IContents.ICheckpointModel[]> {
+    return this._manager.listCheckpoints(this.id);
+  }
+
   /**
    * Get the list of running sessions.
    */
@@ -353,7 +381,6 @@ class ContextManager implements IDisposable {
    *
    * @param options - If given, change the kernel (starting a session
    * if necessary). If falsey, shut down any existing session and return
-   * a void promise.
    */
   changeKernel(id: string, options: IKernel.IModel): Promise<IKernel> {
     let contextEx = this._contexts[id];
@@ -526,6 +553,47 @@ class ContextManager implements IDisposable {
     this._contexts[id].context.populated.emit(void 0);
   }
 
+  /**
+   * Create a checkpoint for a file.
+   */
+  createCheckpoint(id: string): Promise<IContents.ICheckpointModel> {
+    let path = this._contexts[id].path;
+    return this._manager.contents.createCheckpoint(path);
+  }
+
+  /**
+   * Delete a checkpoint for a file.
+   */
+  deleteCheckpoint(id: string, checkpointID: string): Promise<void> {
+    let path = this._contexts[id].path;
+    return this._manager.contents.deleteCheckpoint(path, checkpointID);
+  }
+
+  /**
+   * Restore a file to a known checkpoint state.
+   */
+  restoreCheckpoint(id: string, checkpointID?: string): Promise<void> {
+    let path = this._contexts[id].path;
+    if (checkpointID) {
+      return this._manager.contents.restoreCheckpoint(path, checkpointID);
+    }
+    return this.listCheckpoints(id).then(checkpoints => {
+      if (!checkpoints.length) {
+        return;
+      }
+      checkpointID = checkpoints[checkpoints.length - 1].id;
+      return this._manager.contents.restoreCheckpoint(path, checkpointID);
+    });
+  }
+
+  /**
+   * List available checkpoints for a file.
+   */
+  listCheckpoints(id: string): Promise<IContents.ICheckpointModel[]> {
+    let path = this._contexts[id].path;
+    return this._manager.contents.listCheckpoints(path);
+  }
+
   /**
    * Get the list of running sessions.
    */

+ 145 - 0
src/docmanager/savehandler.ts

@@ -0,0 +1,145 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  IServiceManager
+} from 'jupyter-js-services';
+
+import {
+okButton, cancelButton, showDialog
+} from '../dialog';
+
+import {
+  IDocumentContext, IDocumentModel
+} from '../docregistry';
+
+
+/**
+ * A class that manages the auto saving of a document.
+ *
+ * #### Notes
+ * Implements https://github.com/ipython/ipython/wiki/IPEP-15:-Autosaving-the-IPython-Notebook.
+ */
+export
+class SaveHandler {
+  /**
+   * Construct a new save handler.
+   */
+  constructor(options: SaveHandler.IOptions) {
+    this._services = options.services;
+    this._context = options.context;
+    this._minInterval = options.saveInterval || 120;
+    this._interval = this._minInterval;
+  }
+
+  /**
+   * Start the autosaver.
+   */
+  start(): void {
+    clearTimeout(this._autosaveTimer);
+    setTimeout(() => this._save(), this._interval * 1000);
+  }
+
+  /**
+   * Stop the autosaver.
+   */
+  stop(): void {
+    clearTimeout(this._autosaveTimer);
+  }
+
+  /**
+   * Handle an autosave timeout.
+   */
+  private _save(): void {
+    let context = this._context;
+    // Bail if the model is not dirty or it is read only.
+    if (!this._context.model.dirty || this._context.model.readOnly) {
+      // Trigger the next update.
+      this.start();
+      return;
+    }
+
+    // Make sure the file has not changed on disk.
+    this._services.contents.get(context.path).then(model => {
+      if (model.last_modified !== context.contentsModel.last_modified) {
+        return this._timeConflict(model.last_modified);
+      }
+      return this._finishSave();
+    }).then(() => {
+      this.start();
+    }).catch(err => {
+      console.error('Error in Auto-Save', err);
+    });
+  }
+
+  /**
+   * Handle a time conflict.
+   */
+  private _timeConflict(modified: string): Promise<void> {
+    let localTime = new Date(this._context.contentsModel.last_modified);
+    let remoteTime = new Date(modified);
+    console.warn(`Last saving peformed ${localTime} ` +
+                 `while the current file seem to have been saved ` +
+                 `${remoteTime}`);
+    let body = `The file has changed on disk since the last time we ` +
+               `opened or saved it. ` +
+               `Do you want to overwrite the file on disk with the version ` +
+               ` open here, or load the version on disk (revert)?`;
+    return showDialog({
+      title: 'File Changed', body, okText: 'OVERWRITE',
+      buttons: [cancelButton, { text: 'REVERT' }, okButton]
+    }).then(result => {
+      if (result.text === 'OVERWRITE') {
+        return this._finishSave();
+      } else if (result.text === 'REVERT') {
+        return this._context.revert();
+      }
+    });
+  }
+
+  /**
+   * Perform the save, adjusting the save interval as necessary.
+   */
+  private _finishSave(): Promise<void> {
+    let start = new Date().getTime();
+    return this._context.save().then(() => {
+      let duration = new Date().getTime() - start;
+      // New save interval: higher of 10x save duration or min interval.
+      this._interval = Math.max(10 * duration, this._minInterval);
+    });
+  }
+
+  private _autosaveTimer = -1;
+  private _minInterval = -1;
+  private _interval = -1;
+  private _context: IDocumentContext<IDocumentModel> = null;
+  private _services: IServiceManager = null;
+}
+
+
+/**
+ * A namespace for `SaveHandler` statics.
+ */
+export
+namespace SaveHandler {
+  /**
+   * The options used to create a save handler.
+   */
+  export
+  interface IOptions {
+    /**
+     * The context asssociated with the file.
+     */
+    context: IDocumentContext<IDocumentModel>;
+
+    /**
+     * The service manager to use for checking last saved.
+     */
+    services: IServiceManager;
+
+    /**
+     * The minimum save interval in seconds (default is two minutes).
+     */
+    saveInterval?: number;
+  }
+}

+ 35 - 0
src/docregistry/interfaces.ts

@@ -201,6 +201,41 @@ interface IDocumentContext<T extends IDocumentModel> extends IDisposable {
    */
   revert(): Promise<void>;
 
+  /**
+   * Create a checkpoint for the file.
+   *
+   * @returns A promise which resolves with the new checkpoint model when the
+   *   checkpoint is created.
+   */
+  createCheckpoint(): Promise<IContents.ICheckpointModel>;
+
+  /**
+   * Delete a checkpoint for the file.
+   *
+   * @param checkpointID - The id of the checkpoint to delete.
+   *
+   * @returns A promise which resolves when the checkpoint is deleted.
+   */
+  deleteCheckpoint(checkpointID: string): Promise<void>;
+
+  /**
+   * Restore the file to a known checkpoint state.
+   *
+   * @param checkpointID - The optional id of the checkpoint to restore,
+   *   defaults to the most recent checkpoint.
+   *
+   * @returns A promise which resolves when the checkpoint is restored.
+   */
+  restoreCheckpoint(checkpointID?: string): Promise<void>;
+
+  /**
+   * List available checkpoints for the file.
+   *
+   * @returns A promise which resolves with a list of checkpoint models for
+   *    the file.
+   */
+  listCheckpoints(): Promise<IContents.ICheckpointModel[]>;
+
   /**
    * Get the list of running sessions.
    */