Generate deterministic range ids based on seed

This commit is contained in:
James Allen 2017-01-09 10:46:58 +01:00
parent 2c7029cc50
commit 7cac2f7d76
3 changed files with 23 additions and 49 deletions

View file

@ -8,6 +8,8 @@ module.exports = RangesManager =
rangesTracker = new RangesTracker(changes, comments) rangesTracker = new RangesTracker(changes, comments)
for update in updates for update in updates
rangesTracker.track_changes = !!update.meta.tc rangesTracker.track_changes = !!update.meta.tc
if !!update.meta.tc
rangesTracker.setIdSeed(update.meta.tc)
for op in update.op for op in update.op
rangesTracker.applyOp(op, { user_id: update.meta?.user_id }) rangesTracker.applyOp(op, { user_id: update.meta?.user_id })

View file

@ -35,18 +35,19 @@ load = (EventEmitter) ->
# * Inserts by another user will not combine with inserts by the first user. If they are in the # * Inserts by another user will not combine with inserts by the first user. If they are in the
# middle of a previous insert by the first user, the original insert will be split into two. # middle of a previous insert by the first user, the original insert will be split into two.
constructor: (@changes = [], @comments = []) -> constructor: (@changes = [], @comments = []) ->
# Change objects have the following structure:
# { getIdSeed: () ->
# id: ... # Uniquely generated by us return @id_seed
# op: { # ShareJs style op tracking the offset (p) and content inserted (i) or deleted (d)
# i: "..." setIdSeed: (seed) ->
# p: 42 @id_seed = seed
# } @id_increment = 0
# }
# newId: () ->
# Ids are used to uniquely identify a change, e.g. for updating it in the database, or keeping in @id_increment++
# sync with Ace ranges. increment = @id_increment.toString(16)
@id = 0 id = @id_seed + '000000'.substr(0, 6 - increment.length) + increment;
return id
getComment: (comment_id) -> getComment: (comment_id) ->
comment = null comment = null
@ -56,19 +57,6 @@ load = (EventEmitter) ->
break break
return comment return comment
resolveCommentId: (comment_id, resolved_data) ->
comment = @getComment(comment_id)
return if !comment?
comment.metadata.resolved = true
comment.metadata.resolved_data = resolved_data
@emit "comment:resolved", comment
unresolveCommentId: (comment_id) ->
comment = @getComment(comment_id)
return if !comment?
comment.metadata.resolved = false
@emit "comment:unresolved", comment
removeCommentId: (comment_id) -> removeCommentId: (comment_id) ->
comment = @getComment(comment_id) comment = @getComment(comment_id)
return if !comment? return if !comment?
@ -88,7 +76,7 @@ load = (EventEmitter) ->
return if !change? return if !change?
@_removeChange(change) @_removeChange(change)
applyOp: (op, metadata) -> applyOp: (op, metadata = {}) ->
metadata.ts ?= new Date() metadata.ts ?= new Date()
# Apply an op that has been applied to the document to our changes to keep them up to date # Apply an op that has been applied to the document to our changes to keep them up to date
if op.i? if op.i?
@ -105,7 +93,7 @@ load = (EventEmitter) ->
addComment: (op, metadata) -> addComment: (op, metadata) ->
# TODO: Don't allow overlapping comments? # TODO: Don't allow overlapping comments?
@comments.push comment = { @comments.push comment = {
id: @_newId() id: @newId()
op: # Copy because we'll modify in place op: # Copy because we'll modify in place
c: op.c c: op.c
p: op.p p: op.p
@ -394,28 +382,9 @@ load = (EventEmitter) ->
if moved_changes.length > 0 if moved_changes.length > 0
@emit "changes:moved", moved_changes @emit "changes:moved", moved_changes
_newId: () ->
# Generate a Mongo ObjectId
# Reference: https://github.com/dreampulse/ObjectId.js/blob/master/src/main/javascript/Objectid.js
@_pid ?= Math.floor(Math.random() * (32767))
@_machine ?= Math.floor(Math.random() * (16777216))
timestamp = Math.floor(new Date().valueOf() / 1000)
@_increment ?= 0
@_increment++
timestamp = timestamp.toString(16)
machine = @_machine.toString(16)
pid = @_pid.toString(16)
increment = @_increment.toString(16)
return '00000000'.substr(0, 8 - timestamp.length) + timestamp +
'000000'.substr(0, 6 - machine.length) + machine +
'0000'.substr(0, 4 - pid.length) + pid +
'000000'.substr(0, 6 - increment.length) + increment;
_addOp: (op, metadata) -> _addOp: (op, metadata) ->
change = { change = {
id: @_newId() id: @newId()
op: op op: op
metadata: metadata metadata: metadata
} }

View file

@ -12,6 +12,7 @@ describe "Ranges", ->
before (done) -> before (done) ->
@project_id = DocUpdaterClient.randomId() @project_id = DocUpdaterClient.randomId()
@user_id = DocUpdaterClient.randomId() @user_id = DocUpdaterClient.randomId()
@id_seed = "587357bd35e64f6157"
@doc = { @doc = {
id: DocUpdaterClient.randomId() id: DocUpdaterClient.randomId()
lines: ["aaa"] lines: ["aaa"]
@ -25,7 +26,7 @@ describe "Ranges", ->
doc: @doc.id doc: @doc.id
op: [{ i: "456", p: 5 }] op: [{ i: "456", p: 5 }]
v: 1 v: 1
meta: { user_id: @user_id, tc: 1 } meta: { user_id: @user_id, tc: @id_seed }
}, { }, {
doc: @doc.id doc: @doc.id
op: [{ d: "12", p: 1 }] op: [{ d: "12", p: 1 }]
@ -52,6 +53,7 @@ describe "Ranges", ->
ranges = data.ranges ranges = data.ranges
change = ranges.changes[0] change = ranges.changes[0]
change.op.should.deep.equal { i: "456", p: 3 } change.op.should.deep.equal { i: "456", p: 3 }
change.id.should.equal @id_seed + "000001"
change.metadata.user_id.should.equal @user_id change.metadata.user_id.should.equal @user_id
done() done()
@ -135,6 +137,7 @@ describe "Ranges", ->
before (done) -> before (done) ->
@project_id = DocUpdaterClient.randomId() @project_id = DocUpdaterClient.randomId()
@user_id = DocUpdaterClient.randomId() @user_id = DocUpdaterClient.randomId()
@id_seed = "587357bd35e64f6157"
@doc = { @doc = {
id: DocUpdaterClient.randomId() id: DocUpdaterClient.randomId()
lines: ["a123aa"] lines: ["a123aa"]
@ -143,7 +146,7 @@ describe "Ranges", ->
doc: @doc.id doc: @doc.id
op: [{ i: "456", p: 5 }] op: [{ i: "456", p: 5 }]
v: 0 v: 0
meta: { user_id: @user_id, tc: 1 } meta: { user_id: @user_id, tc: @id_seed }
} }
MockWebApi.insertDoc @project_id, @doc.id, { MockWebApi.insertDoc @project_id, @doc.id, {
lines: @doc.lines lines: @doc.lines