overleaf/services/document-updater/app/coffee/UpdateManager.coffee

103 lines
4.9 KiB
CoffeeScript
Raw Normal View History

2014-02-12 05:40:42 -05:00
LockManager = require "./LockManager"
RedisManager = require "./RedisManager"
WebRedisManager = require "./WebRedisManager"
2014-02-12 05:40:42 -05:00
ShareJsUpdateManager = require "./ShareJsUpdateManager"
2016-11-28 05:14:42 -05:00
HistoryManager = require "./HistoryManager"
2014-02-12 05:40:42 -05:00
Settings = require('settings-sharelatex')
async = require("async")
logger = require('logger-sharelatex')
Metrics = require "./Metrics"
2016-11-28 05:14:42 -05:00
Errors = require "./Errors"
DocumentManager = require "./DocumentManager"
RangesManager = require "./RangesManager"
2014-02-12 05:40:42 -05:00
module.exports = UpdateManager =
processOutstandingUpdates: (project_id, doc_id, callback = (error) ->) ->
2014-02-12 05:40:42 -05:00
timer = new Metrics.Timer("updateManager.processOutstandingUpdates")
UpdateManager.fetchAndApplyUpdates project_id, doc_id, (error) ->
2014-02-12 05:40:42 -05:00
timer.done()
return callback(error) if error?
callback()
2014-02-12 05:40:42 -05:00
processOutstandingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
LockManager.tryLock doc_id, (error, gotLock, lockValue) =>
2014-02-12 05:40:42 -05:00
return callback(error) if error?
return callback() if !gotLock
UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
LockManager.releaseLock doc_id, lockValue, (error) =>
2014-02-12 05:40:42 -05:00
return callback(error) if error?
UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id, callback
continueProcessingUpdatesWithLock: (project_id, doc_id, callback = (error) ->) ->
WebRedisManager.getUpdatesLength doc_id, (error, length) =>
2014-02-12 05:40:42 -05:00
return callback(error) if error?
if length > 0
UpdateManager.processOutstandingUpdatesWithLock project_id, doc_id, callback
else
callback()
fetchAndApplyUpdates: (project_id, doc_id, callback = (error) ->) ->
WebRedisManager.getPendingUpdatesForDoc doc_id, (error, updates) =>
2014-02-12 05:40:42 -05:00
return callback(error) if error?
if updates.length == 0
return callback()
2016-09-09 10:28:27 -04:00
async.eachSeries updates,
(update, cb) -> UpdateManager.applyUpdate project_id, doc_id, update, cb
callback
2014-02-12 05:40:42 -05:00
applyUpdate: (project_id, doc_id, update, _callback = (error) ->) ->
callback = (error) ->
if error?
WebRedisManager.sendData {project_id, doc_id, error: error.message || error}
_callback(error)
UpdateManager._sanitizeUpdate update
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
2014-02-12 05:40:42 -05:00
return callback(error) if error?
2016-11-28 05:14:42 -05:00
if !lines? or !version?
return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
ShareJsUpdateManager.applyUpdate project_id, doc_id, update, lines, version, (error, updatedDocLines, version, appliedOps) ->
return callback(error) if error?
RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, (error, new_ranges) ->
2016-11-28 05:14:42 -05:00
return callback(error) if error?
RedisManager.updateDocument doc_id, updatedDocLines, version, appliedOps, new_ranges, (error) ->
2016-11-28 05:14:42 -05:00
return callback(error) if error?
HistoryManager.pushUncompressedHistoryOps project_id, doc_id, appliedOps, callback
2014-02-12 05:40:42 -05:00
lockUpdatesAndDo: (method, project_id, doc_id, args..., callback) ->
LockManager.getLock doc_id, (error, lockValue) ->
2014-02-12 05:40:42 -05:00
return callback(error) if error?
UpdateManager.processOutstandingUpdates project_id, doc_id, (error) ->
return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
2014-02-12 05:40:42 -05:00
method project_id, doc_id, args..., (error, response_args...) ->
return UpdateManager._handleErrorInsideLock(doc_id, lockValue, error, callback) if error?
LockManager.releaseLock doc_id, lockValue, (error) ->
2014-02-12 05:40:42 -05:00
return callback(error) if error?
callback null, response_args...
# We held the lock for a while so updates might have queued up
UpdateManager.continueProcessingUpdatesWithLock project_id, doc_id
_handleErrorInsideLock: (doc_id, lockValue, original_error, callback = (error) ->) ->
LockManager.releaseLock doc_id, lockValue, (lock_error) ->
2014-02-12 05:40:42 -05:00
callback(original_error)
_sanitizeUpdate: (update) ->
# In Javascript, characters are 16-bits wide. It does not understand surrogates as characters.
#
# From Wikipedia (http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane):
# "The High Surrogates (U+D800U+DBFF) and Low Surrogate (U+DC00U+DFFF) codes are reserved
# for encoding non-BMP characters in UTF-16 by using a pair of 16-bit codes: one High Surrogate
# and one Low Surrogate. A single surrogate code point will never be assigned a character.""
#
# The main offender seems to be \uD835 as a stand alone character, which would be the first
# 16-bit character of a blackboard bold character (http://www.fileformat.info/info/unicode/char/1d400/index.htm).
# Something must be going on client side that is screwing up the encoding and splitting the
# two 16-bit characters so that \uD835 is standalone.
for op in update.op or []
if op.i?
# Replace high and low surrogate characters with 'replacement character' (\uFFFD)
op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
return update
2014-02-12 05:40:42 -05:00