UpdateManager.coffee 4.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  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. TrackChangesManager = require "./TrackChangesManager"
  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. UpdateManager._sanitizeUpdate update
  46. DocumentManager.getDoc project_id, doc_id, (error, lines, version, track_changes_entries) ->
  47. return callback(error) if error?
  48. if !lines? or !version?
  49. return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
  50. ShareJsUpdateManager.applyUpdate project_id, doc_id, update, lines, version, (error, updatedDocLines, version, appliedOps) ->
  51. return callback(error) if error?
  52. TrackChangesManager.applyUpdate project_id, doc_id, track_changes_entries, appliedOps, (error, new_track_changes_entries) ->
  53. return callback(error) if error?
  54. logger.log doc_id: doc_id, version: version, "updating doc in redis"
  55. RedisManager.updateDocument doc_id, updatedDocLines, version, appliedOps, new_track_changes_entries, (error) ->
  56. return callback(error) if error?
  57. HistoryManager.pushUncompressedHistoryOps project_id, doc_id, appliedOps, callback
  58. lockUpdatesAndDo: (method, project_id, doc_id, args..., callback) ->
  59. LockManager.getLock doc_id, (error, lockValue) ->
  60. return callback(error) if error?
  61. UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
  62. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  63. method project_id, doc_id, args..., (error, response_args...) ->
  64. return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
  65. LockManager.releaseLock doc_id, lockValue, (error) ->
  66. return callback(error) if error?
  67. callback null, response_args...
  68. # We held the lock for a while so updates might have queued up
  69. UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id
  70. _handleErrorInsideLock: (doc_id, lockValue, original_error, callback = (error) ->) ->
  71. LockManager.releaseLock doc_id, lockValue, (lock_error) ->
  72. callback(original_error)
  73. _sanitizeUpdate: (update) ->
  74. # In Javascript, characters are 16-bits wide. It does not understand surrogates as characters.
  75. #
  76. # From Wikipedia (http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane):
  77. # "The High Surrogates (U+D800–U+DBFF) and Low Surrogate (U+DC00–U+DFFF) codes are reserved
  78. # for encoding non-BMP characters in UTF-16 by using a pair of 16-bit codes: one High Surrogate
  79. # and one Low Surrogate. A single surrogate code point will never be assigned a character.""
  80. #
  81. # The main offender seems to be \uD835 as a stand alone character, which would be the first
  82. # 16-bit character of a blackboard bold character (http://www.fileformat.info/info/unicode/char/1d400/index.htm).
  83. # Something must be going on client side that is screwing up the encoding and splitting the
  84. # two 16-bit characters so that \uD835 is standalone.
  85. for op in update.op or []
  86. if op.i?
  87. # Replace high and low surrogate characters with 'replacement character' (\uFFFD)
  88. op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
  89. return update