Просмотр исходного кода

Proposal for interface to for a realtime handler.

Ian Rose 8 лет назад
Родитель
Сommit
c3da54189e

+ 1 - 0
packages/coreutils/src/index.ts

@@ -15,3 +15,4 @@ export * from './url';
 export * from './uuid';
 export * from './vector';
 export * from './modeldb';
+export * from './realtime';

+ 122 - 0
packages/coreutils/src/realtime.ts

@@ -0,0 +1,122 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  IDisposable
+} from '@phosphor/disposable';
+
+import {
+  JSONValue, Token
+} from '@phosphor/coreutils';
+
+import {
+  IObservableString
+} from './observablestring';
+
+import {
+  IObservableVector
+} from './observablevector';
+
+import {
+  IObservableMap
+} from './observablemap';
+
+import {
+  IModelDB
+} from './modeldb';
+
+/* tslint:disable */
+/**
+ * The realtime service token.
+ */
+export
+const IRealtime = new Token<IRealtime>('jupyter.services.realtime');
+/* tslint:enable */
+
+
+/**
+ * Interface for a Realtime service.
+ */
+export
+interface IRealtime {
+  /**
+   * The realtime services may require some setup before
+   * it can be used (e.g., loading external APIs, authorization).
+   * This promise is resolved when the services are ready to
+   * be used.
+   */
+  ready: Promise<void>;
+
+  /**
+   * Create an IRealtimeHandler for use with a document or other
+   * model.
+   *
+   * @param path: a path that identifies the location of the realtime
+   *   store in the backend.
+   */
+  createHandler(path: string): IRealtimeHandler;
+}
+
+
+/**
+ * Interface for an object that coordinates realtime collaboration between
+ * objects. These objects are expected to subscribe to the handler using
+ * IRealtimeModel.registerCollaborative( handler : IRealtimeHandller)`.
+ * There should be one realtime handler per realtime model.
+ */
+export
+interface IRealtimeHandler extends IDisposable {
+  /**
+   * A map of the currently active collaborators
+   * for the handler, including the local user.
+   */
+  readonly collaborators: IObservableMap<ICollaborator>;
+
+  /**
+   * The local collaborator.
+   */
+  readonly localCollaborator: ICollaborator;
+
+  /**
+   * An IModelDB for this handler.
+   */
+  readonly modelDB: IModelDB;
+
+  /**
+   * A promise resolved when the handler is ready to
+   * be used.
+   */
+  readonly ready: Promise<void>;
+}
+
+
+/**
+ * Interface for an object representing a single collaborator
+ * on a realtime model.
+ */
+export
+interface ICollaborator {
+  /**
+   * A user id for the collaborator.
+   * This might not be unique, if the user has more than
+   * one editing session at a time.
+   */
+  readonly userId: string;
+
+  /**
+   * A session id, which should be unique to a
+   * particular view on a collaborative model.
+   */
+  readonly sessionId: string;
+
+  /**
+   * A human-readable display name for a collaborator.
+   */
+  readonly displayName: string;
+
+  /**
+   * A color to be used to identify the collaborator in
+   * UI elements.
+   */
+  readonly color: string;
+}

+ 16 - 3
packages/docmanager/src/manager.ts

@@ -33,6 +33,10 @@ import {
   IClientSession
 } from '@jupyterlab/apputils';
 
+import {
+  IRealtime
+} from '@jupyterlab/coreutils';
+
 import {
   DocumentRegistry, Context
 } from '@jupyterlab/docregistry';
@@ -79,6 +83,7 @@ class DocumentManager implements IDisposable {
     this._registry = options.registry;
     this._serviceManager = options.manager;
     this._opener = options.opener;
+    this._realtimeServices = options.realtimeServices;
     this._widgetManager = new DocumentWidgetManager({
       registry: this._registry
     });
@@ -305,7 +310,8 @@ class DocumentManager implements IDisposable {
       manager: this._serviceManager,
       factory,
       path,
-      kernelPreference
+      kernelPreference,
+      realtimeServices: this._realtimeServices
     });
     let handler = new SaveHandler({
       context,
@@ -375,8 +381,9 @@ class DocumentManager implements IDisposable {
       context = this._findContext(path, factory.name);
       if (!context) {
         context = this._createContext(path, factory, preference);
-        // Load the contents from disk.
-        context.revert();
+        // Populate the model, either from disk or a
+        // model backend.
+        context.fromStore();
       }
     } else if (which === 'create') {
       context = this._createContext(path, factory, preference);
@@ -402,6 +409,7 @@ class DocumentManager implements IDisposable {
   private _contexts: Private.IContext[] = [];
   private _opener: DocumentManager.IWidgetOpener = null;
   private _activateRequested = new Signal<this, string>(this);
+  private _realtimeServices: IRealtime;
 }
 
 
@@ -429,6 +437,11 @@ namespace DocumentManager {
      * A widget opener for sibling widgets.
      */
     opener: IWidgetOpener;
+
+    /**
+     * A provider for realtime services.
+     */
+    realtimeServices?: IRealtime;
   }
 
   /**

+ 48 - 2
packages/docregistry/src/context.ts

@@ -26,7 +26,7 @@ import {
 } from '@jupyterlab/apputils';
 
 import {
-  PathExt, URLExt
+  PathExt, URLExt, IModelDB, IRealtime, IRealtimeHandler
 } from '@jupyterlab/coreutils';
 
 import {
@@ -51,7 +51,15 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     this._path = options.path;
     let ext = DocumentRegistry.extname(this._path);
     let lang = this._factory.preferredLanguage(ext);
-    this._model = this._factory.createNew(lang);
+
+    if(options.realtimeServices) {
+      this._realtimeHandler = options.realtimeServices.createHandler(this._path);
+      this._modelDB = this._realtimeHandler.modelDB;
+      this._model = this._factory.createNew(lang, this._modelDB);
+    } else {
+      this._model = this._factory.createNew(lang);
+    }
+
     this._readyPromise = manager.ready.then(() => {
       return this._populatedPromise.promise;
     });
@@ -115,6 +123,13 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     return this._contentsModel;
   }
 
+  /**
+   * The realtime handler associated with the document.
+   */
+  get realtimeHandler(): IRealtimeHandler {
+    return this._realtimeHandler;
+  }
+
   /**
    * Get the model factory name.
    *
@@ -141,9 +156,13 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     }
     let model = this._model;
     this.session.dispose();
+    if (this._realtimeHandler) {
+      this._realtimeHandler.dispose();
+    }
     this._model = null;
     this._manager = null;
     this._factory = null;
+    this._realtimeHandler = null;
 
     model.dispose();
     this._disposed.emit(void 0);
@@ -164,6 +183,26 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
     return this._readyPromise;
   }
 
+  /**
+   * Populate the contents of the model, either from
+   * disk or from the realtime handler.
+   *
+   * @returns a promise that resolves upon model population.
+   */
+  fromStore(): Promise<void> {
+    if (this.realtimeHandler) {
+      return this._modelDB.connected.then(() => {
+        if (this._modelDB.isPrepopulated) {
+          return this.save();
+        } else {
+          return this.revert();
+        }
+      });
+    } else {
+      return this.revert();
+    }
+  }
+
   /**
    * Save the document contents to disk.
    */
@@ -424,6 +463,8 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
   private _manager: ServiceManager.IManager = null;
   private _opener: (widget: Widget) => void = null;
   private _model: T = null;
+  private _modelDB: IModelDB;
+  private _realtimeHandler: IRealtimeHandler = null;
   private _path = '';
   private _factory: DocumentRegistry.IModelFactory<T> = null;
   private _contentsModel: Contents.IModel = null;
@@ -466,6 +507,11 @@ export namespace Context {
      */
     kernelPreference?: IClientSession.IKernelPreference;
 
+    /**
+     * Provider for realtime services.
+     */
+    realtimeServices?: IRealtime;
+
     /**
      * An optional callback for opening sibling widgets.
      */

+ 8 - 1
packages/docregistry/src/registry.ts

@@ -34,7 +34,7 @@ import {
 } from '@jupyterlab/codeeditor';
 
 import {
-  IChangedArgs as IChangedArgsGeneric, PathExt, IModelDB
+  IChangedArgs as IChangedArgsGeneric, PathExt, IModelDB, IRealtimeHandler
 } from '@jupyterlab/coreutils';
 
 /* tslint:disable */
@@ -657,6 +657,13 @@ namespace DocumentRegistry {
      */
     readonly contentsModel: Contents.IModel;
 
+    /**
+     * An optional `IRealtimeHandler for the document.
+     * It will be null unless the `IContext` is given an
+     * `IRealtime` object at creation time.
+     */
+    readonly realtimeHandler: IRealtimeHandler;
+
     /**
      * Whether the context is ready.
      */