From 80b7875414d6d15f1be01b3a98781d5d2b3e28cb Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 14 Nov 2014 16:51:55 +0000 Subject: [PATCH] Add in leaveProject handler --- services/real-time/Gruntfile.coffee | 1 + .../app/coffee/WebsocketController.coffee | 29 ++++++++++ .../coffee/ClientTrackingTests.coffee | 2 +- .../coffee/LeaveProjectTests.coffee | 16 ++++++ .../coffee/WebsocketControllerTests.coffee | 56 +++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 services/real-time/test/acceptance/coffee/LeaveProjectTests.coffee diff --git a/services/real-time/Gruntfile.coffee b/services/real-time/Gruntfile.coffee index e01c8aecb1..28cc4233b0 100644 --- a/services/real-time/Gruntfile.coffee +++ b/services/real-time/Gruntfile.coffee @@ -44,6 +44,7 @@ module.exports = (grunt) -> unit: options: reporter: grunt.option('reporter') or 'spec' + grep: grunt.option("grep") src: ["test/unit/js/**/*.js"] acceptance: options: diff --git a/services/real-time/app/coffee/WebsocketController.coffee b/services/real-time/app/coffee/WebsocketController.coffee index 6f1b3b5119..04a9d926de 100644 --- a/services/real-time/app/coffee/WebsocketController.coffee +++ b/services/real-time/app/coffee/WebsocketController.coffee @@ -3,6 +3,7 @@ WebApiManager = require "./WebApiManager" AuthorizationManager = require "./AuthorizationManager" DocumentUpdaterManager = require "./DocumentUpdaterManager" ConnectedUsersManager = require "./ConnectedUsersManager" +TrackChangesManager = require "./TrackChangesManager" WebsocketLoadBalancer = require "./WebsocketLoadBalancer" Utils = require "./Utils" @@ -40,6 +41,34 @@ module.exports = WebsocketController = # No need to block for setting the user as connected in the cursor tracking ConnectedUsersManager.updateUserPosition project_id, client.id, user, null, () -> + + # We want to flush a project if there are no more (local) connected clients + # but we need to wait for the triggering client to disconnect. How long we wait + # is determined by FLUSH_IF_EMPTY_DELAY. + FLUSH_IF_EMPTY_DELAY: 500 #ms + leaveProject: (io, client, callback = (error) ->) -> + Utils.getClientAttributes client, ["project_id", "user_id"], (error, {project_id, user_id}) -> + return callback(error) if error? + logger.log {project_id, user_id, client_id: client.id}, "client leaving project" + WebsocketLoadBalancer.emitToRoom project_id, "clientTracking.clientDisconnected", client.id + + # We can do this in the background + ConnectedUsersManager.markUserAsDisconnected project_id, client.id, (err) -> + if err? + logger.error {err, project_id, user_id, client_id: client.id}, "error marking client as disconnected" + + setTimeout () -> + remainingClients = io.sockets.clients(project_id) + if remainingClients.length == 0 + # Flush project in the background + DocumentUpdaterManager.flushProjectToMongoAndDelete project_id, (err) -> + if err? + logger.error {err, project_id, user_id, client_id: client.id}, "error flushing to doc updater after leaving project" + TrackChangesManager.flushProject project_id, (err) -> + if err? + logger.error {err, project_id, user_id, client_id: client.id}, "error flushing to track changes after leaving project" + callback() + , WebsocketController.FLUSH_IF_EMPTY_DELAY joinDoc: (client, doc_id, fromVersion = -1, callback = (error, doclines, version, ops) ->) -> Utils.getClientAttributes client, ["project_id", "user_id"], (error, {project_id, user_id}) -> diff --git a/services/real-time/test/acceptance/coffee/ClientTrackingTests.coffee b/services/real-time/test/acceptance/coffee/ClientTrackingTests.coffee index c0f0079bbc..90071e61ec 100644 --- a/services/real-time/test/acceptance/coffee/ClientTrackingTests.coffee +++ b/services/real-time/test/acceptance/coffee/ClientTrackingTests.coffee @@ -48,7 +48,7 @@ describe "clientTracking", -> doc_id: @doc_id = "mock-doc-id" }, (error) -> throw error if error? - done() + setTimeout done, 300 # Give the message a chance to reach client B. it "should tell other clients about the update", -> @updates.should.deep.equal [ diff --git a/services/real-time/test/acceptance/coffee/LeaveProjectTests.coffee b/services/real-time/test/acceptance/coffee/LeaveProjectTests.coffee new file mode 100644 index 0000000000..9698abd2c2 --- /dev/null +++ b/services/real-time/test/acceptance/coffee/LeaveProjectTests.coffee @@ -0,0 +1,16 @@ +describe "leaveProject", -> + describe "with other clients in the project", -> + it "should emit a disconnect message to the room" + + it "should no longer list the client in connected users" + + it "should not flush the project to the document updater" + + it "should not flush the project in track changes" + + describe "with no other clients in the project", -> + it "should flush the project to the document updater" + + it "should flush the project in track changes" + + \ No newline at end of file diff --git a/services/real-time/test/unit/coffee/WebsocketControllerTests.coffee b/services/real-time/test/unit/coffee/WebsocketControllerTests.coffee index db7832cf05..3ff8b28f3b 100644 --- a/services/real-time/test/unit/coffee/WebsocketControllerTests.coffee +++ b/services/real-time/test/unit/coffee/WebsocketControllerTests.coffee @@ -29,6 +29,7 @@ describe 'WebsocketController', -> "./WebApiManager": @WebApiManager = {} "./AuthorizationManager": @AuthorizationManager = {} "./DocumentUpdaterManager": @DocumentUpdaterManager = {} + "./TrackChangesManager": @TrackChangesManager = {} "./ConnectedUsersManager": @ConnectedUsersManager = {} "./WebsocketLoadBalancer": @WebsocketLoadBalancer = {} "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } @@ -108,7 +109,62 @@ describe 'WebsocketController', -> @callback .calledWith(new Error("not authorized")) .should.equal true + + describe "leaveProject", -> + beforeEach -> + @DocumentUpdaterManager.flushProjectToMongoAndDelete = sinon.stub().callsArg(1) + @TrackChangesManager.flushProject = sinon.stub().callsArg(1) + @ConnectedUsersManager.markUserAsDisconnected = sinon.stub().callsArg(2) + @WebsocketLoadBalancer.emitToRoom = sinon.stub() + @clientsInRoom = [] + @io = + sockets: + clients: (room_id) => + if room_id != @project_id + throw "expected room_id to be project_id" + return @clientsInRoom + @client.params.project_id = @project_id + @WebsocketController.FLUSH_IF_EMPTY_DELAY = 0 + tk.reset() # Allow setTimeout to work. + + describe "when the project is empty", -> + beforeEach (done) -> + @clientsInRoom = [] + @WebsocketController.leaveProject @io, @client, done + + it "should end clientTracking.clientDisconnected to the project room", -> + @WebsocketLoadBalancer.emitToRoom + .calledWith(@project_id, "clientTracking.clientDisconnected", @client.id) + .should.equal true + + it "should mark the user as disconnected", -> + @ConnectedUsersManager.markUserAsDisconnected + .calledWith(@project_id, @client.id) + .should.equal true + + it "should flush the project in the document updater", -> + @DocumentUpdaterManager.flushProjectToMongoAndDelete + .calledWith(@project_id) + .should.equal true + it "should flush the changes in the track changes api", -> + @TrackChangesManager.flushProject + .calledWith(@project_id) + .should.equal true + + describe "when the project is not empty", -> + beforeEach -> + @clientsInRoom = ["mock-remaining-client"] + @WebsocketController.leaveProject @io, @client + + it "should not flush the project in the document updater", -> + @DocumentUpdaterManager.flushProjectToMongoAndDelete + .called.should.equal false + + it "should not flush the changes in the track changes api", -> + @TrackChangesManager.flushProject + .called.should.equal false + describe "joinDoc", -> beforeEach -> @doc_id = "doc-id-123"