UpdateManager.coffee 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. LockManager = require "./LockManager"
  2. RedisManager = require "./RedisManager"
  3. WebRedisManager = require "./WebRedisManager"
  4. ShareJsUpdateManager = require "./ShareJsUpdateManager"
  5. HistoryManager = require "./HistoryManager"
  6. Settings = require('settings-sharelatex')
  7. async = require("async")
  8. logger = require('logger-sharelatex')
  9. Metrics = require "./Metrics"
  10. Errors = require "./Errors"
  11. DocumentManager = require "./DocumentManager"
  12. RangesManager = require "./RangesManager"
  13. module.exports = UpdateManager =
  14. processOutstandingUpdates: (project_id, doc_id, callback = (error) ->) ->
  15. timer = new Metrics.Timer("updateManager.processOutstandingUpdates")
  16. UpdateManager.fetchAndApplyUpdates project_id, doc_id, (error) ->
  17. timer.done()
  18. return callback(error) if error?
  19. callback()
  20. processOutstandingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
  21. LockManager.tryLock doc_id, (error, gotLock, lockValue) =>
  22. return callback(error) if error?
  23. return callback() if !gotLock
  24. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  25. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  26. LockManager.releaseLock doc_id, lockValue, (error) =>
  27. return callback(error) if error?
  28. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id, callback
  29. continueProcessingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
  30. WebRedisManager.getUpdatesLength doc_id, (error, length) =>
  31. return callback(error) if error?
  32. if length > 0
  33. UpdateManager.processOutstandingUpdatesWithLock project_id, doc_id, callback
  34. else
  35. callback()
  36. fetchAndApplyUpdates: (project_id, doc_id, callback = (error) ->) ->
  37. WebRedisManager.getPendingUpdatesForDoc doc_id, (error, updates) =>
  38. return callback(error) if error?
  39. if updates.length == 0
  40. return callback()
  41. async.eachSeries updates,
  42. (update, cb) -> UpdateManager.applyUpdate project_id, doc_id, update, cb
  43. callback
  44. applyUpdate: (project_id, doc_id, update, _callback = (error) ->) ->
  45. callback = (error) ->
  46. if error?
  47. WebRedisManager.sendData {project_id, doc_id, error: error.message || error}
  48. _callback(error)
  49. UpdateManager._sanitizeUpdate update
  50. DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
  51. return callback(error) if error?
  52. if !lines? or !version?
  53. return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
  54. ShareJsUpdateManager.applyUpdate project_id, doc_id, update, lines, version, (error, updatedDocLines, version, appliedOps) ->
  55. return callback(error) if error?
  56. RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, (error, new_ranges) ->
  57. return callback(error) if error?
  58. RedisManager.updateDocument doc_id, updatedDocLines, version, appliedOps, new_ranges, (error) ->
  59. return callback(error) if error?
  60. HistoryManager.pushUncompressedHistoryOps project_id, doc_id, appliedOps, callback
  61. lockUpdatesAndDo: (method, project_id, doc_id, args..., callback) ->
  62. LockManager.getLock doc_id, (error, lockValue) ->
  63. return callback(error) if error?
  64. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  65. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  66. method project_id, doc_id, args..., (error, response_args...) ->
  67. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  68. LockManager.releaseLock doc_id, lockValue, (error) ->
  69. return callback(error) if error?
  70. callback null, response_args...
  71. # We held the lock for a while so updates might have queued up
  72. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id
  73. _handleErrorInsideLock: (doc_id, lockValue, original_error, callback = (error) ->) ->
  74. LockManager.releaseLock doc_id, lockValue, (lock_error) ->
  75. callback(original_error)
  76. _sanitizeUpdate: (update) ->
  77. # In Javascript, characters are 16-bits wide. It does not understand surrogates as characters.
  78. #
  79. # From Wikipedia (http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane):
  80. # "The High Surrogates (U+D800–U+DBFF) and Low Surrogate (U+DC00–U+DFFF) codes are reserved
  81. # for encoding non-BMP characters in UTF-16 by using a pair of 16-bit codes: one High Surrogate
  82. # and one Low Surrogate. A single surrogate code point will never be assigned a character.""
  83. #
  84. # The main offender seems to be \uD835 as a stand alone character, which would be the first
  85. # 16-bit character of a blackboard bold character (http://www.fileformat.info/info/unicode/char/1d400/index.htm).
  86. # Something must be going on client side that is screwing up the encoding and splitting the
  87. # two 16-bit characters so that \uD835 is standalone.
  88. for op in update.op or []
  89. if op.i?
  90. # Replace high and low surrogate characters with 'replacement character' (\uFFFD)
  91. op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
  92. return update