cursor data is stored in redis

This commit is contained in:
Henry Oswald 2014-07-14 15:30:55 +01:00
parent 61b7bea203
commit d13676dab2
4 changed files with 73 additions and 28 deletions

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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 ->