mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-19 09:45:37 -04:00
Move lib and test into src directory
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
241c418ea7
commit
fab2607e4d
98 changed files with 1 additions and 3 deletions
312
src/lib/ot/client.js
Executable file
312
src/lib/ot/client.js
Executable file
|
@ -0,0 +1,312 @@
|
|||
// translation of https://github.com/djspiewak/cccp/blob/master/agent/src/main/scala/com/codecommit/cccp/agent/state.scala
|
||||
|
||||
if (typeof ot === 'undefined') {
|
||||
var ot = {};
|
||||
}
|
||||
|
||||
ot.Client = (function (global) {
|
||||
'use strict';
|
||||
|
||||
// Client constructor
|
||||
function Client (revision) {
|
||||
this.revision = revision; // the next expected revision number
|
||||
this.setState(synchronized_); // start state
|
||||
}
|
||||
|
||||
Client.prototype.setState = function (state) {
|
||||
this.state = state;
|
||||
};
|
||||
|
||||
// Call this method when the user changes the document.
|
||||
Client.prototype.applyClient = function (operation) {
|
||||
this.setState(this.state.applyClient(this, operation));
|
||||
};
|
||||
|
||||
// Call this method with a new operation from the server
|
||||
Client.prototype.applyServer = function (revision, operation) {
|
||||
this.setState(this.state.applyServer(this, revision, operation));
|
||||
};
|
||||
|
||||
Client.prototype.applyOperations = function (head, operations) {
|
||||
this.setState(this.state.applyOperations(this, head, operations));
|
||||
};
|
||||
|
||||
Client.prototype.serverAck = function (revision) {
|
||||
this.setState(this.state.serverAck(this, revision));
|
||||
};
|
||||
|
||||
Client.prototype.serverReconnect = function () {
|
||||
if (typeof this.state.resend === 'function') { this.state.resend(this); }
|
||||
};
|
||||
|
||||
// Transforms a selection from the latest known server state to the current
|
||||
// client state. For example, if we get from the server the information that
|
||||
// another user's cursor is at position 3, but the server hasn't yet received
|
||||
// our newest operation, an insertion of 5 characters at the beginning of the
|
||||
// document, the correct position of the other user's cursor in our current
|
||||
// document is 8.
|
||||
Client.prototype.transformSelection = function (selection) {
|
||||
return this.state.transformSelection(selection);
|
||||
};
|
||||
|
||||
// Override this method.
|
||||
Client.prototype.sendOperation = function (revision, operation) {
|
||||
throw new Error("sendOperation must be defined in child class");
|
||||
};
|
||||
|
||||
// Override this method.
|
||||
Client.prototype.applyOperation = function (operation) {
|
||||
throw new Error("applyOperation must be defined in child class");
|
||||
};
|
||||
|
||||
|
||||
// In the 'Synchronized' state, there is no pending operation that the client
|
||||
// has sent to the server.
|
||||
function Synchronized () {}
|
||||
Client.Synchronized = Synchronized;
|
||||
|
||||
Synchronized.prototype.applyClient = function (client, operation) {
|
||||
// When the user makes an edit, send the operation to the server and
|
||||
// switch to the 'AwaitingConfirm' state
|
||||
client.sendOperation(client.revision, operation);
|
||||
return new AwaitingConfirm(operation);
|
||||
};
|
||||
|
||||
Synchronized.prototype.applyServer = function (client, revision, operation) {
|
||||
if (revision - client.revision > 1) {
|
||||
throw new Error("Invalid revision.");
|
||||
}
|
||||
client.revision = revision;
|
||||
// When we receive a new operation from the server, the operation can be
|
||||
// simply applied to the current document
|
||||
client.applyOperation(operation);
|
||||
return this;
|
||||
};
|
||||
|
||||
Synchronized.prototype.serverAck = function (client, revision) {
|
||||
throw new Error("There is no pending operation.");
|
||||
};
|
||||
|
||||
// Nothing to do because the latest server state and client state are the same.
|
||||
Synchronized.prototype.transformSelection = function (x) { return x; };
|
||||
|
||||
// Singleton
|
||||
var synchronized_ = new Synchronized();
|
||||
|
||||
|
||||
// In the 'AwaitingConfirm' state, there's one operation the client has sent
|
||||
// to the server and is still waiting for an acknowledgement.
|
||||
function AwaitingConfirm (outstanding) {
|
||||
// Save the pending operation
|
||||
this.outstanding = outstanding;
|
||||
}
|
||||
Client.AwaitingConfirm = AwaitingConfirm;
|
||||
|
||||
AwaitingConfirm.prototype.applyClient = function (client, operation) {
|
||||
// When the user makes an edit, don't send the operation immediately,
|
||||
// instead switch to 'AwaitingWithBuffer' state
|
||||
return new AwaitingWithBuffer(this.outstanding, operation);
|
||||
};
|
||||
|
||||
AwaitingConfirm.prototype.applyServer = function (client, revision, operation) {
|
||||
if (revision - client.revision > 1) {
|
||||
throw new Error("Invalid revision.");
|
||||
}
|
||||
client.revision = revision;
|
||||
// This is another client's operation. Visualization:
|
||||
//
|
||||
// /\
|
||||
// this.outstanding / \ operation
|
||||
// / \
|
||||
// \ /
|
||||
// pair[1] \ / pair[0] (new outstanding)
|
||||
// (can be applied \/
|
||||
// to the client's
|
||||
// current document)
|
||||
var pair = operation.constructor.transform(this.outstanding, operation);
|
||||
client.applyOperation(pair[1]);
|
||||
return new AwaitingConfirm(pair[0]);
|
||||
};
|
||||
|
||||
AwaitingConfirm.prototype.serverAck = function (client, revision) {
|
||||
if (revision - client.revision > 1) {
|
||||
return new Stale(this.outstanding, client, revision).getOperations();
|
||||
}
|
||||
client.revision = revision;
|
||||
// The client's operation has been acknowledged
|
||||
// => switch to synchronized state
|
||||
return synchronized_;
|
||||
};
|
||||
|
||||
AwaitingConfirm.prototype.transformSelection = function (selection) {
|
||||
return selection.transform(this.outstanding);
|
||||
};
|
||||
|
||||
AwaitingConfirm.prototype.resend = function (client) {
|
||||
// The confirm didn't come because the client was disconnected.
|
||||
// Now that it has reconnected, we resend the outstanding operation.
|
||||
client.sendOperation(client.revision, this.outstanding);
|
||||
};
|
||||
|
||||
|
||||
// In the 'AwaitingWithBuffer' state, the client is waiting for an operation
|
||||
// to be acknowledged by the server while buffering the edits the user makes
|
||||
function AwaitingWithBuffer (outstanding, buffer) {
|
||||
// Save the pending operation and the user's edits since then
|
||||
this.outstanding = outstanding;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
Client.AwaitingWithBuffer = AwaitingWithBuffer;
|
||||
|
||||
AwaitingWithBuffer.prototype.applyClient = function (client, operation) {
|
||||
// Compose the user's changes onto the buffer
|
||||
var newBuffer = this.buffer.compose(operation);
|
||||
return new AwaitingWithBuffer(this.outstanding, newBuffer);
|
||||
};
|
||||
|
||||
AwaitingWithBuffer.prototype.applyServer = function (client, revision, operation) {
|
||||
if (revision - client.revision > 1) {
|
||||
throw new Error("Invalid revision.");
|
||||
}
|
||||
client.revision = revision;
|
||||
// Operation comes from another client
|
||||
//
|
||||
// /\
|
||||
// this.outstanding / \ operation
|
||||
// / \
|
||||
// /\ /
|
||||
// this.buffer / \* / pair1[0] (new outstanding)
|
||||
// / \/
|
||||
// \ /
|
||||
// pair2[1] \ / pair2[0] (new buffer)
|
||||
// the transformed \/
|
||||
// operation -- can
|
||||
// be applied to the
|
||||
// client's current
|
||||
// document
|
||||
//
|
||||
// * pair1[1]
|
||||
var transform = operation.constructor.transform;
|
||||
var pair1 = transform(this.outstanding, operation);
|
||||
var pair2 = transform(this.buffer, pair1[1]);
|
||||
client.applyOperation(pair2[1]);
|
||||
return new AwaitingWithBuffer(pair1[0], pair2[0]);
|
||||
};
|
||||
|
||||
AwaitingWithBuffer.prototype.serverAck = function (client, revision) {
|
||||
if (revision - client.revision > 1) {
|
||||
return new StaleWithBuffer(this.outstanding, this.buffer, client, revision).getOperations();
|
||||
}
|
||||
client.revision = revision;
|
||||
// The pending operation has been acknowledged
|
||||
// => send buffer
|
||||
client.sendOperation(client.revision, this.buffer);
|
||||
return new AwaitingConfirm(this.buffer);
|
||||
};
|
||||
|
||||
AwaitingWithBuffer.prototype.transformSelection = function (selection) {
|
||||
return selection.transform(this.outstanding).transform(this.buffer);
|
||||
};
|
||||
|
||||
AwaitingWithBuffer.prototype.resend = function (client) {
|
||||
// The confirm didn't come because the client was disconnected.
|
||||
// Now that it has reconnected, we resend the outstanding operation.
|
||||
client.sendOperation(client.revision, this.outstanding);
|
||||
};
|
||||
|
||||
|
||||
function Stale(acknowlaged, client, revision) {
|
||||
this.acknowlaged = acknowlaged;
|
||||
this.client = client;
|
||||
this.revision = revision;
|
||||
}
|
||||
Client.Stale = Stale;
|
||||
|
||||
Stale.prototype.applyClient = function (client, operation) {
|
||||
return new StaleWithBuffer(this.acknowlaged, operation, client, this.revision);
|
||||
};
|
||||
|
||||
Stale.prototype.applyServer = function (client, revision, operation) {
|
||||
throw new Error("Ignored server-side change.");
|
||||
};
|
||||
|
||||
Stale.prototype.applyOperations = function (client, head, operations) {
|
||||
var transform = this.acknowlaged.constructor.transform;
|
||||
for (var i = 0; i < operations.length; i++) {
|
||||
var op = ot.TextOperation.fromJSON(operations[i]);
|
||||
var pair = transform(this.acknowlaged, op);
|
||||
client.applyOperation(pair[1]);
|
||||
this.acknowlaged = pair[0];
|
||||
}
|
||||
client.revision = this.revision;
|
||||
return synchronized_;
|
||||
};
|
||||
|
||||
Stale.prototype.serverAck = function (client, revision) {
|
||||
throw new Error("There is no pending operation.");
|
||||
};
|
||||
|
||||
Stale.prototype.transformSelection = function (selection) {
|
||||
return selection;
|
||||
};
|
||||
|
||||
Stale.prototype.getOperations = function () {
|
||||
this.client.getOperations(this.client.revision, this.revision - 1); // acknowlaged is the one at revision
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
function StaleWithBuffer(acknowlaged, buffer, client, revision) {
|
||||
this.acknowlaged = acknowlaged;
|
||||
this.buffer = buffer;
|
||||
this.client = client;
|
||||
this.revision = revision;
|
||||
}
|
||||
Client.StaleWithBuffer = StaleWithBuffer;
|
||||
|
||||
StaleWithBuffer.prototype.applyClient = function (client, operation) {
|
||||
var buffer = this.buffer.compose(operation);
|
||||
return new StaleWithBuffer(this.acknowlaged, buffer, client, this.revision);
|
||||
};
|
||||
|
||||
StaleWithBuffer.prototype.applyServer = function (client, revision, operation) {
|
||||
throw new Error("Ignored server-side change.");
|
||||
};
|
||||
|
||||
StaleWithBuffer.prototype.applyOperations = function (client, head, operations) {
|
||||
var transform = this.acknowlaged.constructor.transform;
|
||||
for (var i = 0; i < operations.length; i++) {
|
||||
var op = ot.TextOperation.fromJSON(operations[i]);
|
||||
var pair1 = transform(this.acknowlaged, op);
|
||||
var pair2 = transform(this.buffer, pair1[1]);
|
||||
client.applyOperation(pair2[1]);
|
||||
this.acknowlaged = pair1[0];
|
||||
this.buffer = pair2[0];
|
||||
}
|
||||
client.revision = this.revision;
|
||||
client.sendOperation(client.revision, this.buffer);
|
||||
return new AwaitingConfirm(this.buffer);
|
||||
};
|
||||
|
||||
StaleWithBuffer.prototype.serverAck = function (client, revision) {
|
||||
throw new Error("There is no pending operation.");
|
||||
};
|
||||
|
||||
StaleWithBuffer.prototype.transformSelection = function (selection) {
|
||||
return selection;
|
||||
};
|
||||
|
||||
StaleWithBuffer.prototype.getOperations = function () {
|
||||
this.client.getOperations(this.client.revision, this.revision - 1); // acknowlaged is the one at revision
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
return Client;
|
||||
|
||||
}(this));
|
||||
|
||||
if (typeof module === 'object') {
|
||||
module.exports = ot.Client;
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue