2014-02-12 05:40:42 -05:00
|
|
|
ShareJsModel = require "./sharejs/server/model"
|
|
|
|
ShareJsDB = require "./ShareJsDB"
|
|
|
|
logger = require "logger-sharelatex"
|
|
|
|
Settings = require('settings-sharelatex')
|
2016-06-07 12:58:18 -04:00
|
|
|
Keys = require "./UpdateKeys"
|
2014-02-12 05:40:42 -05:00
|
|
|
{EventEmitter} = require "events"
|
|
|
|
util = require "util"
|
2017-05-02 10:38:33 -04:00
|
|
|
RealTimeRedisManager = require "./RealTimeRedisManager"
|
2019-04-08 08:43:24 -04:00
|
|
|
crypto = require "crypto"
|
2014-02-12 05:40:42 -05:00
|
|
|
|
|
|
|
ShareJsModel:: = {}
|
|
|
|
util.inherits ShareJsModel, EventEmitter
|
|
|
|
|
2017-01-17 05:45:10 -05:00
|
|
|
MAX_AGE_OF_OP = 80
|
|
|
|
|
2014-02-12 05:40:42 -05:00
|
|
|
module.exports = ShareJsUpdateManager =
|
2016-11-28 05:14:42 -05:00
|
|
|
getNewShareJsModel: (project_id, doc_id, lines, version) ->
|
|
|
|
db = new ShareJsDB(project_id, doc_id, lines, version)
|
2017-01-17 05:45:10 -05:00
|
|
|
model = new ShareJsModel(db, maxDocLength: Settings.max_doc_length, maximumAge: MAX_AGE_OF_OP)
|
2016-08-23 11:00:46 -04:00
|
|
|
model.db = db
|
|
|
|
return model
|
2014-02-12 05:40:42 -05:00
|
|
|
|
2016-11-28 05:14:42 -05:00
|
|
|
applyUpdate: (project_id, doc_id, update, lines, version, callback = (error, updatedDocLines) ->) ->
|
2016-09-09 06:01:14 -04:00
|
|
|
logger.log project_id: project_id, doc_id: doc_id, update: update, "applying sharejs updates"
|
2014-02-12 05:40:42 -05:00
|
|
|
jobs = []
|
2019-04-09 04:20:48 -04:00
|
|
|
# record the update version before it is modified
|
|
|
|
incomingUpdateVersion = update.version
|
2014-02-12 05:40:42 -05:00
|
|
|
# We could use a global model for all docs, but we're hitting issues with the
|
|
|
|
# internal state of ShareJS not being accessible for clearing caches, and
|
|
|
|
# getting stuck due to queued callbacks (line 260 of sharejs/server/model.coffee)
|
|
|
|
# This adds a small but hopefully acceptable overhead (~12ms per 1000 updates on
|
|
|
|
# my 2009 MBP).
|
2016-11-28 05:14:42 -05:00
|
|
|
model = @getNewShareJsModel(project_id, doc_id, lines, version)
|
2014-02-12 05:40:42 -05:00
|
|
|
@_listenForOps(model)
|
|
|
|
doc_key = Keys.combineProjectIdAndDocId(project_id, doc_id)
|
2016-09-09 06:01:14 -04:00
|
|
|
model.applyOp doc_key, update, (error) ->
|
2014-02-12 05:40:42 -05:00
|
|
|
if error?
|
2016-09-09 06:01:14 -04:00
|
|
|
if error == "Op already submitted"
|
|
|
|
logger.warn {project_id, doc_id, update}, "op has already been submitted"
|
|
|
|
update.dup = true
|
|
|
|
ShareJsUpdateManager._sendOp(project_id, doc_id, update)
|
|
|
|
else
|
|
|
|
return callback(error)
|
|
|
|
logger.log project_id: project_id, doc_id: doc_id, error: error, "applied update"
|
2014-02-12 05:40:42 -05:00
|
|
|
model.getSnapshot doc_key, (error, data) =>
|
2017-01-10 10:58:11 -05:00
|
|
|
return callback(error) if error?
|
2019-04-08 08:43:24 -04:00
|
|
|
# only check hash when present and no other updates have been applied
|
2019-04-09 04:20:48 -04:00
|
|
|
if update.hash? and incomingUpdateVersion == version
|
2019-04-08 08:43:24 -04:00
|
|
|
ourHash = ShareJsUpdateManager._computeHash(data.snapshot)
|
|
|
|
return callback(new Error("Invalid hash")) if ourHash != update.hash
|
2014-04-10 07:44:46 -04:00
|
|
|
docLines = data.snapshot.split(/\r\n|\n|\r/)
|
2016-08-23 11:00:46 -04:00
|
|
|
callback(null, docLines, data.v, model.db.appliedOps[doc_key] or [])
|
2014-02-12 05:40:42 -05:00
|
|
|
|
|
|
|
_listenForOps: (model) ->
|
|
|
|
model.on "applyOp", (doc_key, opData) ->
|
|
|
|
[project_id, doc_id] = Keys.splitProjectIdAndDocId(doc_key)
|
2015-11-19 05:54:28 -05:00
|
|
|
ShareJsUpdateManager._sendOp(project_id, doc_id, opData)
|
|
|
|
|
2016-11-28 05:14:42 -05:00
|
|
|
_sendOp: (project_id, doc_id, op) ->
|
2017-05-02 10:38:33 -04:00
|
|
|
RealTimeRedisManager.sendData {project_id, doc_id, op}
|
2014-02-12 05:40:42 -05:00
|
|
|
|
2019-04-08 08:43:24 -04:00
|
|
|
_computeHash: (content) ->
|
|
|
|
return crypto.createHash('sha1')
|
|
|
|
.update("blob " + content.length + "\x00")
|
|
|
|
.update(content, 'utf8')
|
|
|
|
.digest('hex')
|
|
|
|
|