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

cursor data is stored in redis

Henry Oswald 12 лет назад
Родитель
Сommit
d13676dab2

+ 22 - 9
services/web/app/coffee/Features/ConnectedUsers/ConnectedUsersManager.coffee

@@ -11,6 +11,8 @@ ONE_HOUR_IN_S = 60 * 60
 ONE_DAY_IN_S = ONE_HOUR_IN_S * 24
 FOUR_DAYS_IN_S = ONE_DAY_IN_S * 4
 
+USER_TIMEOUT_IN_S = ONE_HOUR_IN_S
+
 buildProjectSetKey = (project_id)-> return "users_in_project:#{project_id}"
 buildUserKey = (project_id, user_id)-> return "connected_user:#{project_id}:#{user_id}"
 
@@ -25,7 +27,9 @@ module.exports =
 			(cb)->
 				rclient.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S, cb
 			(cb)->
-				rclient.setex buildUserKey(project_id, user_id), ONE_HOUR_IN_S, new Date(), cb
+				rclient.hset buildUserKey(project_id, user_id), "connected_at", new Date(), cb
+			(cb)->
+				rclient.expire buildUserKey(project_id, user_id), USER_TIMEOUT_IN_S, cb
 		], (err)->
 			if err?
 				logger.err err:err, project_id:project_id, user_id:user_id, "problem marking user as connected"
@@ -43,13 +47,25 @@ module.exports =
 		], callback
 
 	_getConnectedUser: (project_id, user_id, callback)->
-		rclient.get buildUserKey(project_id, user_id), (err, result)->
+		rclient.hgetall buildUserKey(project_id, user_id), (err, result)->
 			if !result?
-				connected = false
+				result =
+					connected : false
+					user_id:user_id
 			else
-				connected = true
+				result.connected = true
+				result.user_id = user_id
+
+			callback err, result
+
+	setUserCursorPosition: (project_id, user_id, cursorData, callback)->
+		async.series [
+			(cb)->
+				rclient.hset buildUserKey(project_id, user_id), "cursorData", JSON.stringify(cursorData), cb
+			(cb)->
+				rclient.expire buildUserKey(project_id, user_id), USER_TIMEOUT_IN_S, cb
+		], callback
 
-			callback err, {connected:connected, user_id:user_id}
 
 	getConnectedUsers: (project_id, callback)->
 		self = @
@@ -60,8 +76,5 @@ module.exports =
 			async.series jobs, (err, users)->
 				users = _.filter users, (user)->
 					user.connected
-				user_ids = _.map users, (user)->
-					user.user_id
-				callback err, user_ids
-
+				callback err, users
 

+ 1 - 0
services/web/app/coffee/Features/Editor/EditorController.coffee

@@ -126,6 +126,7 @@ module.exports = EditorController =
 			cursorData.email = email if email?
 			if first_name? and last_name?
 				cursorData.name = first_name + " " + last_name
+				ConnectedUsersManager.setUserCursorPosition(project_id, user_id, cursorData, ->)
 			else
 				cursorData.name = "Anonymous"
 			EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)

+ 27 - 6
services/web/test/UnitTests/coffee/ConnectedUsersManager/ConnectedUsersManagerTests.coffee

@@ -25,6 +25,8 @@ describe "ConnectedUsersManager", ->
 			del:sinon.stub()
 			smembers:sinon.stub()
 			expire:sinon.stub()
+			hset:sinon.stub()
+			hgetall:sinon.stub()
 		tk.freeze(new Date())
 
 		@ConnectedUsersManager = SandboxedModule.require modulePath, requires:
@@ -40,14 +42,14 @@ describe "ConnectedUsersManager", ->
 
 	describe "markUserAsConnected", ->
 		beforeEach ->
-			@rClient.setex.callsArgWith(3)
+			@rClient.hset.callsArgWith(3)
 			@rClient.sadd.callsArgWith(2)
 			@rClient.expire.callsArgWith(2)
 
 
 		it "should set a key with the date and give it a ttl", (done)->
 			@ConnectedUsersManager.markUserAsConnected @project_id, @user_id, (err)=>
-				@rClient.setex.calledWith("connected_user:#{@project_id}:#{@user_id}", 60 * 60, new Date()).should.equal true
+				@rClient.hset.calledWith("connected_user:#{@project_id}:#{@user_id}", "connected_at", new Date()).should.equal true
 				done()
 
 		it "should push the user_id on to the project list", (done)->
@@ -60,6 +62,11 @@ describe "ConnectedUsersManager", ->
 				@rClient.expire.calledWith("users_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
 				done()
 
+		it "should add a ttl to the connected user so it stays clean", (done)->
+			@ConnectedUsersManager.markUserAsConnected @project_id, @user_id, (err)=>
+				@rClient.expire.calledWith("connected_user:#{@project_id}:#{@user_id}", 60 * 60).should.equal true
+				done()
+
 	describe "markUserAsDisconnected", ->
 		beforeEach ->
 			@rClient.srem.callsArgWith(2)
@@ -85,14 +92,14 @@ describe "ConnectedUsersManager", ->
 	describe "_getConnectedUser", ->
 
 		it "should get the user returning connected if there is a value", (done)->
-			@rClient.get.callsArgWith(1, null, new Date())
+			@rClient.hgetall.callsArgWith(1, null, {connected_at:new Date(), cursorData:{row:1}})
 			@ConnectedUsersManager._getConnectedUser @project_id, @user_id, (err, result)=>
 				result.connected.should.equal true
 				result.user_id.should.equal @user_id
 				done()
 
 		it "should get the user returning connected if there is a value", (done)->
-			@rClient.get.callsArgWith(1)
+			@rClient.hgetall.callsArgWith(1)
 			@ConnectedUsersManager._getConnectedUser @project_id, @user_id, (err, result)=>
 				result.connected.should.equal false
 				result.user_id.should.equal @user_id
@@ -114,11 +121,25 @@ describe "ConnectedUsersManager", ->
 		it "should only return the users in the list which are still in redis", (done)->
 			@ConnectedUsersManager.getConnectedUsers @project_id, (err, users)=>
 				users.length.should.equal 2
-				users[0].should.equal @users[0]
-				users[1].should.equal @users[2]
+				users[0].should.deep.equal {user_id:@users[0], connected:true}
+				users[1].should.deep.equal {user_id:@users[2], connected:true}
 				done()
 
+	describe "setUserCursorPosition", ->
+
+		beforeEach ->
+			@cursorData = { row: 12, column: 9, doc_id: '53c3b8c85fee64000023dc6e' }
+			@rClient.hset.callsArgWith(3)
+			@rClient.expire.callsArgWith(2)
 
+		it "should add the cursor data to the users hash", (done)->
+			@ConnectedUsersManager.setUserCursorPosition @project_id, @user_id, @cursorData, (err)=>
+				@rClient.hset.calledWith("connected_user:#{@project_id}:#{@user_id}", "cursorData", JSON.stringify(@cursorData)).should.equal true
+				done()
 
 
+		it "should add the ttl on", (done)->
+			@ConnectedUsersManager.setUserCursorPosition @project_id, @user_id, @cursorData, (err)=>
+				@rClient.expire.calledWith("connected_user:#{@project_id}:#{@user_id}", 60 * 60).should.equal true
+				done()
 

+ 21 - 11
services/web/test/UnitTests/coffee/Editor/EditorControllerTests.coffee

@@ -56,6 +56,7 @@ describe "EditorController", ->
 		@ConnectedUsersManager =
 			markUserAsDisconnected:sinon.stub()
 			markUserAsConnected:sinon.stub()
+			setUserCursorPosition:sinon.stub()
 
 		@EditorController = SandboxedModule.require modulePath, requires:
 			"../../infrastructure/Server" : io : @io
@@ -262,11 +263,14 @@ describe "EditorController", ->
 	describe "updateClientPosition", ->
 		beforeEach ->
 			@EditorRealTimeController.emitToRoom = sinon.stub()
+			@ConnectedUsersManager.setUserCursorPosition.callsArgWith(3)
 			@update = {
 				doc_id: @doc_id = "doc-id-123"
 				row: @row = 42
 				column: @column = 37
 			}
+
+
 		describe "with a logged in user", ->
 			beforeEach ->
 				@clientParams = {
@@ -279,18 +283,21 @@ describe "EditorController", ->
 				@client.get = (param, callback) => callback null, @clientParams[param]
 				@EditorController.updateClientPosition @client, @update
 
+				@populatedCursorData = 
+					doc_id: @doc_id,
+					id: @client.id
+					name: "#{@first_name} #{@last_name}"
+					row: @row
+					column: @column
+					email: @email
+					user_id: @user_id
+
 			it "should send the update to the project room with the user's name", ->
-				@EditorRealTimeController.emitToRoom
-					.calledWith(@project_id, "clientTracking.clientUpdated", {
-						doc_id: @doc_id,
-						id: @client.id
-						name: "#{@first_name} #{@last_name}"
-						row: @row
-						column: @column
-						email: @email
-						user_id: @user_id
-					})
-					.should.equal true
+				@EditorRealTimeController.emitToRoom.calledWith(@project_id, "clientTracking.clientUpdated", @populatedCursorData).should.equal true
+
+			it "should send the  cursor data to the connected user manager", (done)->
+				@ConnectedUsersManager.setUserCursorPosition.calledWith(@project_id, @user_id, @populatedCursorData).should.equal true
+				done()
 
 		describe "with an anonymous user", ->
 			beforeEach ->
@@ -311,6 +318,9 @@ describe "EditorController", ->
 					})
 					.should.equal true
 				
+			it "should not send cursor data to the connected user manager", (done)->
+				@ConnectedUsersManager.setUserCursorPosition.called.should.equal false
+				done()
 
 	describe "addUserToProject", ->
 		beforeEach ->