UpdateCompressor.coffee 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. strInject = (s1, pos, s2) -> s1[...pos] + s2 + s1[pos..]
  2. strRemove = (s1, pos, length) -> s1[...pos] + s1[(pos + length)..]
  3. module.exports = UpdateCompressor =
  4. # Updates come from the doc updater in format
  5. # {
  6. # op: [ { ... op1 ... }, { ... op2 ... } ]
  7. # meta: { ts: ..., user_id: ... }
  8. # }
  9. # but it's easier to work with on op per update, so convert these updates to
  10. # our compressed format
  11. # [{
  12. # op: op1
  13. # meta: { start_ts: ... , end_ts: ..., user_id: ... }
  14. # }, {
  15. # op: op2
  16. # meta: { start_ts: ... , end_ts: ..., user_id: ... }
  17. # }]
  18. convertToSingleOpUpdates: (updates) ->
  19. splitUpdates = []
  20. for update in updates
  21. for op in update.op
  22. splitUpdates.push
  23. op: op
  24. meta:
  25. start_ts: update.meta.start_ts or update.meta.ts
  26. end_ts: update.meta.end_ts or update.meta.ts
  27. user_id: update.meta.user_id
  28. v: update.v
  29. return splitUpdates
  30. concatUpdatesWithSameVersion: (updates) ->
  31. concattedUpdates = []
  32. for update in updates
  33. lastUpdate = concattedUpdates[concattedUpdates.length - 1]
  34. if lastUpdate? and lastUpdate.v == update.v
  35. lastUpdate.op.push update.op
  36. else
  37. concattedUpdates.push
  38. op: [ update.op ]
  39. meta: update.meta
  40. v: update.v
  41. return concattedUpdates
  42. compressRawUpdates: (lastPreviousUpdate, rawUpdates) ->
  43. if lastPreviousUpdate?
  44. rawUpdates = [lastPreviousUpdate].concat(rawUpdates)
  45. updates = UpdateCompressor.convertToSingleOpUpdates(rawUpdates)
  46. updates = UpdateCompressor.compressUpdates(updates)
  47. return UpdateCompressor.concatUpdatesWithSameVersion(updates)
  48. compressUpdates: (updates) ->
  49. return [] if updates.length == 0
  50. compressedUpdates = [updates.shift()]
  51. for update in updates
  52. lastCompressedUpdate = compressedUpdates.pop()
  53. if lastCompressedUpdate?
  54. compressedUpdates = compressedUpdates.concat UpdateCompressor._concatTwoUpdates lastCompressedUpdate, update
  55. else
  56. compressedUpdates.push update
  57. return compressedUpdates
  58. MAX_TIME_BETWEEN_UPDATES: oneMinute = 60 * 1000
  59. _concatTwoUpdates: (firstUpdate, secondUpdate) ->
  60. firstUpdate =
  61. op: firstUpdate.op
  62. meta:
  63. user_id: firstUpdate.meta.user_id or null
  64. start_ts: firstUpdate.meta.start_ts or firstUpdate.meta.ts
  65. end_ts: firstUpdate.meta.end_ts or firstUpdate.meta.ts
  66. v: firstUpdate.v
  67. secondUpdate =
  68. op: secondUpdate.op
  69. meta:
  70. user_id: secondUpdate.meta.user_id or null
  71. start_ts: secondUpdate.meta.start_ts or secondUpdate.meta.ts
  72. end_ts: secondUpdate.meta.end_ts or secondUpdate.meta.ts
  73. v: secondUpdate.v
  74. if firstUpdate.meta.user_id != secondUpdate.meta.user_id
  75. return [firstUpdate, secondUpdate]
  76. if secondUpdate.meta.start_ts - firstUpdate.meta.end_ts > UpdateCompressor.MAX_TIME_BETWEEN_UPDATES
  77. return [firstUpdate, secondUpdate]
  78. firstOp = firstUpdate.op
  79. secondOp = secondUpdate.op
  80. # Two inserts
  81. if firstOp.i? and secondOp.i? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
  82. return [
  83. meta:
  84. start_ts: firstUpdate.meta.start_ts
  85. end_ts: secondUpdate.meta.end_ts
  86. user_id: firstUpdate.meta.user_id
  87. op:
  88. p: firstOp.p
  89. i: strInject(firstOp.i, secondOp.p - firstOp.p, secondOp.i)
  90. v: secondUpdate.v
  91. ]
  92. # Two deletes
  93. else if firstOp.d? and secondOp.d? and secondOp.p <= firstOp.p <= (secondOp.p + secondOp.d.length)
  94. return [
  95. meta:
  96. start_ts: firstUpdate.meta.start_ts
  97. end_ts: secondUpdate.meta.end_ts
  98. user_id: firstUpdate.meta.user_id
  99. op:
  100. p: secondOp.p
  101. d: strInject(secondOp.d, firstOp.p - secondOp.p, firstOp.d)
  102. v: secondUpdate.v
  103. ]
  104. # An insert and then a delete
  105. else if firstOp.i? and secondOp.d? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
  106. offset = secondOp.p - firstOp.p
  107. insertedText = firstOp.i.slice(offset, offset + secondOp.d.length)
  108. if insertedText == secondOp.d
  109. insert = strRemove(firstOp.i, offset, secondOp.d.length)
  110. return [
  111. meta:
  112. start_ts: firstUpdate.meta.start_ts
  113. end_ts: secondUpdate.meta.end_ts
  114. user_id: firstUpdate.meta.user_id
  115. op:
  116. p: firstOp.p
  117. i: insert
  118. v: secondUpdate.v
  119. ]
  120. else
  121. # This shouldn't be possible!
  122. return [firstUpdate, secondUpdate]
  123. else
  124. return [firstUpdate, secondUpdate]