2014-01-27 11:26:58 -05:00
|
|
|
strInject = (s1, pos, s2) -> s1[...pos] + s2 + s1[pos..]
|
|
|
|
strRemove = (s1, pos, length) -> s1[...pos] + s1[(pos + length)..]
|
|
|
|
|
|
|
|
module.exports = UpdateCompressor =
|
|
|
|
# Updates come from the doc updater in format
|
|
|
|
# {
|
|
|
|
# op: [ { ... op1 ... }, { ... op2 ... } ]
|
|
|
|
# meta: { ts: ..., user_id: ... }
|
|
|
|
# }
|
|
|
|
# but it's easier to work with on op per update, so convert these updates to
|
|
|
|
# our compressed format
|
|
|
|
# [{
|
|
|
|
# op: op1
|
|
|
|
# meta: { start_ts: ... , end_ts: ..., user_id: ... }
|
|
|
|
# }, {
|
|
|
|
# op: op2
|
|
|
|
# meta: { start_ts: ... , end_ts: ..., user_id: ... }
|
|
|
|
# }]
|
2014-03-11 11:24:38 -04:00
|
|
|
convertToSingleOpUpdates: (updates) ->
|
|
|
|
splitUpdates = []
|
2014-01-27 11:26:58 -05:00
|
|
|
for update in updates
|
|
|
|
for op in update.op
|
2014-03-11 11:24:38 -04:00
|
|
|
splitUpdates.push
|
2014-01-27 11:26:58 -05:00
|
|
|
op: op
|
|
|
|
meta:
|
|
|
|
start_ts: update.meta.start_ts or update.meta.ts
|
|
|
|
end_ts: update.meta.end_ts or update.meta.ts
|
|
|
|
user_id: update.meta.user_id
|
2014-02-26 07:11:45 -05:00
|
|
|
v: update.v
|
2014-03-11 11:24:38 -04:00
|
|
|
return splitUpdates
|
|
|
|
|
|
|
|
concatUpdatesWithSameVersion: (updates) ->
|
|
|
|
concattedUpdates = []
|
|
|
|
for update in updates
|
|
|
|
lastUpdate = concattedUpdates[concattedUpdates.length - 1]
|
|
|
|
if lastUpdate? and lastUpdate.v == update.v
|
|
|
|
lastUpdate.op.push update.op
|
|
|
|
else
|
|
|
|
concattedUpdates.push
|
|
|
|
op: [ update.op ]
|
|
|
|
meta: update.meta
|
|
|
|
v: update.v
|
|
|
|
return concattedUpdates
|
2014-01-27 11:26:58 -05:00
|
|
|
|
|
|
|
compressRawUpdates: (lastPreviousUpdate, rawUpdates) ->
|
|
|
|
if lastPreviousUpdate?
|
2014-03-11 11:24:38 -04:00
|
|
|
rawUpdates = [lastPreviousUpdate].concat(rawUpdates)
|
|
|
|
updates = UpdateCompressor.convertToSingleOpUpdates(rawUpdates)
|
|
|
|
updates = UpdateCompressor.compressUpdates(updates)
|
|
|
|
return UpdateCompressor.concatUpdatesWithSameVersion(updates)
|
2014-01-27 11:26:58 -05:00
|
|
|
|
|
|
|
compressUpdates: (updates) ->
|
|
|
|
return [] if updates.length == 0
|
|
|
|
|
|
|
|
compressedUpdates = [updates.shift()]
|
|
|
|
for update in updates
|
|
|
|
lastCompressedUpdate = compressedUpdates.pop()
|
|
|
|
if lastCompressedUpdate?
|
|
|
|
compressedUpdates = compressedUpdates.concat UpdateCompressor._concatTwoUpdates lastCompressedUpdate, update
|
|
|
|
else
|
|
|
|
compressedUpdates.push update
|
|
|
|
|
|
|
|
return compressedUpdates
|
|
|
|
|
|
|
|
MAX_TIME_BETWEEN_UPDATES: oneMinute = 60 * 1000
|
|
|
|
|
|
|
|
_concatTwoUpdates: (firstUpdate, secondUpdate) ->
|
|
|
|
firstUpdate =
|
|
|
|
op: firstUpdate.op
|
|
|
|
meta:
|
|
|
|
user_id: firstUpdate.meta.user_id or null
|
|
|
|
start_ts: firstUpdate.meta.start_ts or firstUpdate.meta.ts
|
|
|
|
end_ts: firstUpdate.meta.end_ts or firstUpdate.meta.ts
|
2014-02-26 07:11:45 -05:00
|
|
|
v: firstUpdate.v
|
2014-01-27 11:26:58 -05:00
|
|
|
secondUpdate =
|
|
|
|
op: secondUpdate.op
|
|
|
|
meta:
|
|
|
|
user_id: secondUpdate.meta.user_id or null
|
|
|
|
start_ts: secondUpdate.meta.start_ts or secondUpdate.meta.ts
|
|
|
|
end_ts: secondUpdate.meta.end_ts or secondUpdate.meta.ts
|
2014-02-26 07:11:45 -05:00
|
|
|
v: secondUpdate.v
|
2014-01-27 11:26:58 -05:00
|
|
|
|
|
|
|
if firstUpdate.meta.user_id != secondUpdate.meta.user_id
|
|
|
|
return [firstUpdate, secondUpdate]
|
|
|
|
|
|
|
|
if secondUpdate.meta.start_ts - firstUpdate.meta.end_ts > UpdateCompressor.MAX_TIME_BETWEEN_UPDATES
|
|
|
|
return [firstUpdate, secondUpdate]
|
|
|
|
|
|
|
|
firstOp = firstUpdate.op
|
|
|
|
secondOp = secondUpdate.op
|
|
|
|
# Two inserts
|
|
|
|
if firstOp.i? and secondOp.i? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
|
|
|
|
return [
|
|
|
|
meta:
|
|
|
|
start_ts: firstUpdate.meta.start_ts
|
|
|
|
end_ts: secondUpdate.meta.end_ts
|
|
|
|
user_id: firstUpdate.meta.user_id
|
|
|
|
op:
|
|
|
|
p: firstOp.p
|
|
|
|
i: strInject(firstOp.i, secondOp.p - firstOp.p, secondOp.i)
|
2014-02-26 07:11:45 -05:00
|
|
|
v: secondUpdate.v
|
2014-01-27 11:26:58 -05:00
|
|
|
]
|
|
|
|
# Two deletes
|
|
|
|
else if firstOp.d? and secondOp.d? and secondOp.p <= firstOp.p <= (secondOp.p + secondOp.d.length)
|
|
|
|
return [
|
|
|
|
meta:
|
|
|
|
start_ts: firstUpdate.meta.start_ts
|
|
|
|
end_ts: secondUpdate.meta.end_ts
|
|
|
|
user_id: firstUpdate.meta.user_id
|
|
|
|
op:
|
|
|
|
p: secondOp.p
|
|
|
|
d: strInject(secondOp.d, firstOp.p - secondOp.p, firstOp.d)
|
2014-02-26 07:11:45 -05:00
|
|
|
v: secondUpdate.v
|
2014-01-27 11:26:58 -05:00
|
|
|
]
|
|
|
|
# An insert and then a delete
|
|
|
|
else if firstOp.i? and secondOp.d? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
|
|
|
|
offset = secondOp.p - firstOp.p
|
|
|
|
insertedText = firstOp.i.slice(offset, offset + secondOp.d.length)
|
|
|
|
if insertedText == secondOp.d
|
|
|
|
insert = strRemove(firstOp.i, offset, secondOp.d.length)
|
|
|
|
return [
|
|
|
|
meta:
|
|
|
|
start_ts: firstUpdate.meta.start_ts
|
|
|
|
end_ts: secondUpdate.meta.end_ts
|
|
|
|
user_id: firstUpdate.meta.user_id
|
|
|
|
op:
|
|
|
|
p: firstOp.p
|
|
|
|
i: insert
|
2014-02-26 07:11:45 -05:00
|
|
|
v: secondUpdate.v
|
2014-01-27 11:26:58 -05:00
|
|
|
]
|
|
|
|
else
|
|
|
|
# This shouldn't be possible!
|
|
|
|
return [firstUpdate, secondUpdate]
|
|
|
|
|
|
|
|
else
|
|
|
|
return [firstUpdate, secondUpdate]
|
|
|
|
|