Преглед изворни кода

Add in leaveProject handler

James Allen пре 12 година
родитељ
комит
80b7875414

+ 1 - 0
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:

+ 29 - 0
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}) ->

+ 1 - 1
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 [

+ 16 - 0
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"
+		
+		

+ 56 - 0
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"