Rename 'track changes entries' -> 'ranges'

This commit is contained in:
James Allen 2016-12-08 12:31:43 +00:00
parent 3ea2e07993
commit e3fee1a1d1
18 changed files with 225 additions and 183 deletions

View file

@ -13,33 +13,33 @@ module.exports = DocumentManager =
timer.done() timer.done()
_callback(args...) _callback(args...)
RedisManager.getDoc project_id, doc_id, (error, lines, version, track_changes_entries) -> RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
return callback(error) if error? return callback(error) if error?
if !lines? or !version? if !lines? or !version?
logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API" logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API"
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, track_changes_entries) -> PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
return callback(error) if error? return callback(error) if error?
logger.log {project_id, doc_id, lines, version}, "got doc from persistence API" logger.log {project_id, doc_id, lines, version}, "got doc from persistence API"
RedisManager.putDocInMemory project_id, doc_id, lines, version, track_changes_entries, (error) -> RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, (error) ->
return callback(error) if error? return callback(error) if error?
callback null, lines, version, track_changes_entries, false callback null, lines, version, ranges, false
else else
callback null, lines, version, track_changes_entries, true callback null, lines, version, ranges, true
getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, recentOps, track_changes_entries) ->) -> getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, recentOps, ranges) ->) ->
timer = new Metrics.Timer("docManager.getDocAndRecentOps") timer = new Metrics.Timer("docManager.getDocAndRecentOps")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
_callback(args...) _callback(args...)
DocumentManager.getDoc project_id, doc_id, (error, lines, version, track_changes_entries) -> DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
return callback(error) if error? return callback(error) if error?
if fromVersion == -1 if fromVersion == -1
callback null, lines, version, [], track_changes_entries callback null, lines, version, [], ranges
else else
RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) -> RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) ->
return callback(error) if error? return callback(error) if error?
callback null, lines, version, ops, track_changes_entries callback null, lines, version, ops, ranges
setDoc: (project_id, doc_id, newLines, source, user_id, _callback = (error) ->) -> setDoc: (project_id, doc_id, newLines, source, user_id, _callback = (error) ->) ->
timer = new Metrics.Timer("docManager.setDoc") timer = new Metrics.Timer("docManager.setDoc")
@ -51,7 +51,7 @@ module.exports = DocumentManager =
return callback(new Error("No lines were provided to setDoc")) return callback(new Error("No lines were provided to setDoc"))
UpdateManager = require "./UpdateManager" UpdateManager = require "./UpdateManager"
DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, track_changes, alreadyLoaded) -> DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, ranges, alreadyLoaded) ->
return callback(error) if error? return callback(error) if error?
if oldLines? and oldLines.length > 0 and oldLines[0].text? if oldLines? and oldLines.length > 0 and oldLines[0].text?
@ -90,14 +90,14 @@ module.exports = DocumentManager =
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
_callback(args...) _callback(args...)
RedisManager.getDoc project_id, doc_id, (error, lines, version, track_changes_entries) -> RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
return callback(error) if error? return callback(error) if error?
if !lines? or !version? if !lines? or !version?
logger.log project_id: project_id, doc_id: doc_id, "doc is not loaded so not flushing" logger.log project_id: project_id, doc_id: doc_id, "doc is not loaded so not flushing"
callback null # TODO: return a flag to bail out, as we go on to remove doc from memory? callback null # TODO: return a flag to bail out, as we go on to remove doc from memory?
else else
logger.log project_id: project_id, doc_id: doc_id, version: version, "flushing doc" logger.log project_id: project_id, doc_id: doc_id, version: version, "flushing doc"
PersistenceManager.setDoc project_id, doc_id, lines, version, track_changes_entries, (error) -> PersistenceManager.setDoc project_id, doc_id, lines, version, ranges, (error) ->
return callback(error) if error? return callback(error) if error?
callback null callback null

View file

@ -18,7 +18,7 @@ module.exports = HttpController =
else else
fromVersion = -1 fromVersion = -1
DocumentManager.getDocAndRecentOpsWithLock project_id, doc_id, fromVersion, (error, lines, version, ops, track_changes_entries) -> DocumentManager.getDocAndRecentOpsWithLock project_id, doc_id, fromVersion, (error, lines, version, ops, ranges) ->
timer.done() timer.done()
return next(error) if error? return next(error) if error?
logger.log project_id: project_id, doc_id: doc_id, "got doc via http" logger.log project_id: project_id, doc_id: doc_id, "got doc via http"
@ -29,7 +29,7 @@ module.exports = HttpController =
lines: lines lines: lines
version: version version: version
ops: ops ops: ops
track_changes_entries: track_changes_entries ranges: ranges
_getTotalSizeOfLines: (lines) -> _getTotalSizeOfLines: (lines) ->
size = 0 size = 0

View file

@ -10,7 +10,7 @@ logger = require "logger-sharelatex"
MAX_HTTP_REQUEST_LENGTH = 5000 # 5 seconds MAX_HTTP_REQUEST_LENGTH = 5000 # 5 seconds
module.exports = PersistenceManager = module.exports = PersistenceManager =
getDoc: (project_id, doc_id, _callback = (error, lines, version, track_changes_entries) ->) -> getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges) ->) ->
timer = new Metrics.Timer("persistenceManager.getDoc") timer = new Metrics.Timer("persistenceManager.getDoc")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
@ -39,13 +39,13 @@ module.exports = PersistenceManager =
return callback(new Error("web API response had no doc lines")) return callback(new Error("web API response had no doc lines"))
if !body.version? or not body.version instanceof Number if !body.version? or not body.version instanceof Number
return callback(new Error("web API response had no valid doc version")) return callback(new Error("web API response had no valid doc version"))
return callback null, body.lines, body.version, body.track_changes_entries return callback null, body.lines, body.version, body.ranges
else if res.statusCode == 404 else if res.statusCode == 404
return callback(new Errors.NotFoundError("doc not not found: #{url}")) return callback(new Errors.NotFoundError("doc not not found: #{url}"))
else else
return callback(new Error("error accessing web API: #{url} #{res.statusCode}")) return callback(new Error("error accessing web API: #{url} #{res.statusCode}"))
setDoc: (project_id, doc_id, lines, version, track_changes_entries, _callback = (error) ->) -> setDoc: (project_id, doc_id, lines, version, ranges, _callback = (error) ->) ->
timer = new Metrics.Timer("persistenceManager.setDoc") timer = new Metrics.Timer("persistenceManager.setDoc")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
@ -57,7 +57,7 @@ module.exports = PersistenceManager =
method: "POST" method: "POST"
json: json:
lines: lines lines: lines
track_changes_entries: track_changes_entries ranges: ranges
version: version version: version
auth: auth:
user: Settings.apis.web.user user: Settings.apis.web.user

View file

@ -56,31 +56,3 @@ module.exports = ProjectManager =
callback new Error("Errors deleting docs. See log for details") callback new Error("Errors deleting docs. See log for details")
else else
callback(null) callback(null)
setTrackChangesWithLocks: (project_id, track_changes_on, _callback = (error) ->) ->
timer = new Metrics.Timer("projectManager.toggleTrackChangesWithLocks")
callback = (args...) ->
timer.done()
_callback(args...)
RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
return callback(error) if error?
jobs = []
errors = []
for doc_id in (doc_ids or [])
do (doc_id) ->
jobs.push (callback) ->
DocumentManager.setTrackChangesWithLock project_id, doc_id, track_changes_on, (error) ->
if error?
logger.error {err: error, project_id, doc_ids, track_changes_on}, "error toggle track changes for doc"
errors.push(error)
callback()
# TODO: If no docs, turn on track changes in Mongo manually
logger.log {project_id, doc_ids, track_changes_on}, "toggling track changes for docs"
async.series jobs, () ->
if errors.length > 0
callback new Error("Errors toggling track changes for docs. See log for details")
else
callback(null)

View file

@ -0,0 +1,24 @@
RangesTracker = require "./RangesTracker"
logger = require "logger-sharelatex"
module.exports = RangesManager =
applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
{changes, comments} = entries
logger.log {changes, comments, updates}, "appliyng updates to ranges"
rangesTracker = new RangesTracker(changes, comments)
for update in updates
rangesTracker.track_changes = !!update.meta.tc
for op in update.op
rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
# Return the minimal data structure needed, since most documents won't have any
# changes or comments
response = {}
if rangesTracker.changes?.length > 0
response ?= {}
response.changes = rangesTracker.changes
if rangesTracker.comments?.length > 0
response ?= {}
response.comments = rangesTracker.comments
logger.log {response}, "applied updates to ranges"
callback null, response

View file

@ -1,5 +1,5 @@
load = (EventEmitter) -> load = (EventEmitter) ->
class ChangesTracker extends EventEmitter class RangesTracker extends EventEmitter
# The purpose of this class is to track a set of inserts and deletes to a document, like # The purpose of this class is to track a set of inserts and deletes to a document, like
# track changes in Word. We store these as a set of ShareJs style ranges: # track changes in Word. We store these as a set of ShareJs style ranges:
# {i: "foo", p: 42} # Insert 'foo' at offset 42 # {i: "foo", p: 42} # Insert 'foo' at offset 42
@ -97,7 +97,7 @@ load = (EventEmitter) ->
return if !change? return if !change?
@_removeChange(change) @_removeChange(change)
applyOp: (op, metadata = {}) -> applyOp: (op, metadata) ->
metadata.ts ?= new Date() metadata.ts ?= new Date()
# Apply an op that has been applied to the document to our changes to keep them up to date # Apply an op that has been applied to the document to our changes to keep them up to date
if op.i? if op.i?
@ -371,7 +371,23 @@ load = (EventEmitter) ->
@emit "changes:moved", moved_changes @emit "changes:moved", moved_changes
_newId: () -> _newId: () ->
(@id++).toString() # Generate a Mongo ObjectId
# Reference: https://github.com/dreampulse/ObjectId.js/blob/master/src/main/javascript/Objectid.js
@_pid ?= Math.floor(Math.random() * (32767))
@_machine ?= Math.floor(Math.random() * (16777216))
timestamp = Math.floor(new Date().valueOf() / 1000)
@_increment ?= 0
@_increment++
timestamp = timestamp.toString(16)
machine = @_machine.toString(16)
pid = @_pid.toString(16)
increment = @_increment.toString(16)
return '00000000'.substr(0, 8 - timestamp.length) + timestamp +
'000000'.substr(0, 6 - machine.length) + machine +
'0000'.substr(0, 4 - pid.length) + pid +
'000000'.substr(0, 6 - increment.length) + increment;
_addOp: (op, metadata) -> _addOp: (op, metadata) ->
change = { change = {

View file

@ -34,10 +34,8 @@ module.exports = RedisKeyBuilder =
return (key_schema) -> key_schema.uncompressedHistoryOp({doc_id}) return (key_schema) -> key_schema.uncompressedHistoryOp({doc_id})
pendingUpdates: ({doc_id}) -> pendingUpdates: ({doc_id}) ->
return (key_schema) -> key_schema.pendingUpdates({doc_id}) return (key_schema) -> key_schema.pendingUpdates({doc_id})
trackChangesEnabled: ({doc_id}) -> ranges: ({doc_id}) ->
return (key_schema) -> key_schema.trackChangesEnabled({doc_id}) return (key_schema) -> key_schema.ranges({doc_id})
trackChangesEntries: ({doc_id}) ->
return (key_schema) -> key_schema.trackChangesEntries({doc_id})
docsInProject: ({project_id}) -> docsInProject: ({project_id}) ->
return (key_schema) -> key_schema.docsInProject({project_id}) return (key_schema) -> key_schema.docsInProject({project_id})
docsWithHistoryOps: ({project_id}) -> docsWithHistoryOps: ({project_id}) ->

View file

@ -13,17 +13,21 @@ minutes = 60 # seconds for Redis expire
module.exports = RedisManager = module.exports = RedisManager =
rclient: rclient rclient: rclient
putDocInMemory : (project_id, doc_id, docLines, version, track_changes_entries, _callback)-> putDocInMemory : (project_id, doc_id, docLines, version, ranges, _callback)->
timer = new metrics.Timer("redis.put-doc") timer = new metrics.Timer("redis.put-doc")
callback = (error) -> callback = (error) ->
timer.done() timer.done()
_callback(error) _callback(error)
logger.log project_id:project_id, doc_id:doc_id, version: version, "putting doc in redis" logger.log project_id:project_id, doc_id:doc_id, version: version, "putting doc in redis"
ranges = RedisManager._serializeRanges(ranges)
multi = rclient.multi() multi = rclient.multi()
multi.set keys.docLines(doc_id:doc_id), JSON.stringify(docLines) multi.set keys.docLines(doc_id:doc_id), JSON.stringify(docLines)
multi.set keys.projectKey({doc_id:doc_id}), project_id multi.set keys.projectKey({doc_id:doc_id}), project_id
multi.set keys.docVersion(doc_id:doc_id), version multi.set keys.docVersion(doc_id:doc_id), version
multi.set keys.trackChangesEntries(doc_id:doc_id), JSON.stringify(track_changes_entries) if ranges?
multi.set keys.ranges(doc_id:doc_id), ranges
else
multi.del keys.ranges(doc_id:doc_id)
multi.exec (error) -> multi.exec (error) ->
return callback(error) if error? return callback(error) if error?
rclient.sadd keys.docsInProject(project_id:project_id), doc_id, callback rclient.sadd keys.docsInProject(project_id:project_id), doc_id, callback
@ -42,32 +46,33 @@ module.exports = RedisManager =
multi.del keys.docLines(doc_id:doc_id) multi.del keys.docLines(doc_id:doc_id)
multi.del keys.projectKey(doc_id:doc_id) multi.del keys.projectKey(doc_id:doc_id)
multi.del keys.docVersion(doc_id:doc_id) multi.del keys.docVersion(doc_id:doc_id)
multi.del keys.trackChangesEntries(doc_id:doc_id) multi.del keys.ranges(doc_id:doc_id)
multi.exec (error) -> multi.exec (error) ->
return callback(error) if error? return callback(error) if error?
rclient.srem keys.docsInProject(project_id:project_id), doc_id, callback rclient.srem keys.docsInProject(project_id:project_id), doc_id, callback
getDoc : (project_id, doc_id, callback = (error, lines, version, track_changes_entries) ->)-> getDoc : (project_id, doc_id, callback = (error, lines, version, ranges) ->)->
timer = new metrics.Timer("redis.get-doc") timer = new metrics.Timer("redis.get-doc")
multi = rclient.multi() multi = rclient.multi()
multi.get keys.docLines(doc_id:doc_id) multi.get keys.docLines(doc_id:doc_id)
multi.get keys.docVersion(doc_id:doc_id) multi.get keys.docVersion(doc_id:doc_id)
multi.get keys.projectKey(doc_id:doc_id) multi.get keys.projectKey(doc_id:doc_id)
multi.get keys.trackChangesEntries(doc_id:doc_id) multi.get keys.ranges(doc_id:doc_id)
multi.exec (error, [docLines, version, doc_project_id, track_changes_entries])-> multi.exec (error, [docLines, version, doc_project_id, ranges])->
timer.done() timer.done()
return callback(error) if error? return callback(error) if error?
try try
docLines = JSON.parse docLines docLines = JSON.parse docLines
track_changes_entries = JSON.parse track_changes_entries ranges = RedisManager._deserializeRanges(ranges)
catch e catch e
return callback(e) return callback(e)
version = parseInt(version or 0, 10) version = parseInt(version or 0, 10)
# check doc is in requested project # check doc is in requested project
if doc_project_id? and doc_project_id isnt project_id if doc_project_id? and doc_project_id isnt project_id
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project" logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project"
return callback(new Errors.NotFoundError("document not found")) return callback(new Errors.NotFoundError("document not found"))
callback null, docLines, version, track_changes_entries callback null, docLines, version, ranges
getDocVersion: (doc_id, callback = (error, version) ->) -> getDocVersion: (doc_id, callback = (error, version) ->) ->
rclient.get keys.docVersion(doc_id: doc_id), (error, version) -> rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
@ -107,7 +112,7 @@ module.exports = RedisManager =
DOC_OPS_TTL: 60 * minutes DOC_OPS_TTL: 60 * minutes
DOC_OPS_MAX_LENGTH: 100 DOC_OPS_MAX_LENGTH: 100
updateDocument : (doc_id, docLines, newVersion, appliedOps = [], track_changes_entries, callback = (error) ->)-> updateDocument : (doc_id, docLines, newVersion, appliedOps = [], ranges, callback = (error) ->)->
RedisManager.getDocVersion doc_id, (error, currentVersion) -> RedisManager.getDocVersion doc_id, (error, currentVersion) ->
return callback(error) if error? return callback(error) if error?
if currentVersion + appliedOps.length != newVersion if currentVersion + appliedOps.length != newVersion
@ -122,10 +127,27 @@ module.exports = RedisManager =
multi.rpush keys.docOps(doc_id: doc_id), jsonOps... multi.rpush keys.docOps(doc_id: doc_id), jsonOps...
multi.expire keys.docOps(doc_id: doc_id), RedisManager.DOC_OPS_TTL multi.expire keys.docOps(doc_id: doc_id), RedisManager.DOC_OPS_TTL
multi.ltrim keys.docOps(doc_id: doc_id), -RedisManager.DOC_OPS_MAX_LENGTH, -1 multi.ltrim keys.docOps(doc_id: doc_id), -RedisManager.DOC_OPS_MAX_LENGTH, -1
multi.set keys.trackChangesEntries(doc_id:doc_id), JSON.stringify(track_changes_entries) ranges = RedisManager._serializeRanges(ranges)
if ranges?
multi.set keys.ranges(doc_id:doc_id), ranges
else
multi.del keys.ranges(doc_id:doc_id)
multi.exec (error, replys) -> multi.exec (error, replys) ->
return callback(error) if error? return callback(error) if error?
return callback() return callback()
getDocIdsInProject: (project_id, callback = (error, doc_ids) ->) -> getDocIdsInProject: (project_id, callback = (error, doc_ids) ->) ->
rclient.smembers keys.docsInProject(project_id: project_id), callback rclient.smembers keys.docsInProject(project_id: project_id), callback
_serializeRanges: (ranges) ->
jsonRanges = JSON.stringify(ranges)
if jsonRanges == '{}'
# Most doc will have empty ranges so don't fill redis with lots of '{}' keys
jsonRanges = null
return jsonRanges
_deserializeRanges: (ranges) ->
if !ranges? or ranges == ""
return {}
else
return JSON.parse(ranges)

View file

@ -1,21 +0,0 @@
ChangesTracker = require "./ChangesTracker"
module.exports = TrackChangesManager =
applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
{changes, comments} = entries
changesTracker = new ChangesTracker(changes, comments)
for update in updates
changesTracker.track_changes = !!update.meta.tc
for op in update.op
changesTracker.applyOp(op, { user_id: update.meta?.user_id })
# Return the minimal data structure needed, since most documents won't have any
# changes or comments
response = null
if changesTracker.changes?.length > 0
response ?= {}
response.changes = changesTracker.changes
if changesTracker.comments?.length > 0
response ?= {}
response.comments = changesTracker.comments
callback null, response

View file

@ -9,7 +9,7 @@ logger = require('logger-sharelatex')
Metrics = require "./Metrics" Metrics = require "./Metrics"
Errors = require "./Errors" Errors = require "./Errors"
DocumentManager = require "./DocumentManager" DocumentManager = require "./DocumentManager"
TrackChangesManager = require "./TrackChangesManager" RangesManager = require "./RangesManager"
module.exports = UpdateManager = module.exports = UpdateManager =
processOutstandingUpdates: (project_id, doc_id, callback = (error) ->) -> processOutstandingUpdates: (project_id, doc_id, callback = (error) ->) ->
@ -48,16 +48,16 @@ module.exports = UpdateManager =
applyUpdate: (project_id, doc_id, update, callback = (error) ->) -> applyUpdate: (project_id, doc_id, update, callback = (error) ->) ->
UpdateManager._sanitizeUpdate update UpdateManager._sanitizeUpdate update
DocumentManager.getDoc project_id, doc_id, (error, lines, version, track_changes_entries) -> DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
return callback(error) if error? return callback(error) if error?
if !lines? or !version? if !lines? or !version?
return callback(new Errors.NotFoundError("document not found: #{doc_id}")) return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
ShareJsUpdateManager.applyUpdate project_id, doc_id, update, lines, version, (error, updatedDocLines, version, appliedOps) -> ShareJsUpdateManager.applyUpdate project_id, doc_id, update, lines, version, (error, updatedDocLines, version, appliedOps) ->
return callback(error) if error? return callback(error) if error?
TrackChangesManager.applyUpdate project_id, doc_id, track_changes_entries, appliedOps, (error, new_track_changes_entries) -> RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, (error, new_ranges) ->
return callback(error) if error? return callback(error) if error?
logger.log doc_id: doc_id, version: version, "updating doc in redis" logger.log doc_id: doc_id, version: version, "updating doc in redis"
RedisManager.updateDocument doc_id, updatedDocLines, version, appliedOps, new_track_changes_entries, (error) -> RedisManager.updateDocument doc_id, updatedDocLines, version, appliedOps, new_ranges, (error) ->
return callback(error) if error? return callback(error) if error?
HistoryManager.pushUncompressedHistoryOps project_id, doc_id, appliedOps, callback HistoryManager.pushUncompressedHistoryOps project_id, doc_id, appliedOps, callback

View file

@ -32,8 +32,7 @@ module.exports =
docVersion: ({doc_id}) -> "DocVersion:#{doc_id}" docVersion: ({doc_id}) -> "DocVersion:#{doc_id}"
projectKey: ({doc_id}) -> "ProjectId:#{doc_id}" projectKey: ({doc_id}) -> "ProjectId:#{doc_id}"
docsInProject: ({project_id}) -> "DocsIn:#{project_id}" docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
trackChangesEnabled: ({doc_id}) -> "TrackChangesEnabled:#{doc_id}" ranges: ({doc_id}) -> "Ranges:#{doc_id}"
trackChangesEntries: ({doc_id}) -> "TrackChangesEntries:#{doc_id}"
# }, { # }, {
# cluster: [{ # cluster: [{
# port: "7000" # port: "7000"
@ -46,8 +45,7 @@ module.exports =
# docVersion: ({doc_id}) -> "DocVersion:{#{doc_id}}" # docVersion: ({doc_id}) -> "DocVersion:{#{doc_id}}"
# projectKey: ({doc_id}) -> "ProjectId:{#{doc_id}}" # projectKey: ({doc_id}) -> "ProjectId:{#{doc_id}}"
# docsInProject: ({project_id}) -> "DocsIn:{#{project_id}}" # docsInProject: ({project_id}) -> "DocsIn:{#{project_id}}"
# trackChangesEnabled: ({doc_id}) -> "TrackChangesEnabled:{#{doc_id}}" # ranges: ({doc_id}) -> "Ranges:{#{doc_id}}"
# trackChangesEntries: ({doc_id}) -> "TrackChangesEntries:{#{doc_id}}"
}] }]
max_doc_length: 2 * 1024 * 1024 # 2mb max_doc_length: 2 * 1024 * 1024 # 2mb

View file

@ -7,8 +7,8 @@ rclient = require("redis").createClient()
MockWebApi = require "./helpers/MockWebApi" MockWebApi = require "./helpers/MockWebApi"
DocUpdaterClient = require "./helpers/DocUpdaterClient" DocUpdaterClient = require "./helpers/DocUpdaterClient"
describe "Track changes", -> describe "Ranges", ->
describe "tracking changes", -> describe "tracking changes from ops", ->
before (done) -> before (done) ->
@project_id = DocUpdaterClient.randomId() @project_id = DocUpdaterClient.randomId()
@user_id = DocUpdaterClient.randomId() @user_id = DocUpdaterClient.randomId()
@ -46,16 +46,16 @@ describe "Track changes", ->
throw error if error? throw error if error?
setTimeout done, 200 setTimeout done, 200
it "should update the tracked entries", (done) -> it "should update the ranges", (done) ->
DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) => DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) =>
throw error if error? throw error if error?
entries = data.track_changes_entries ranges = data.ranges
change = entries.changes[0] change = ranges.changes[0]
change.op.should.deep.equal { i: "456", p: 3 } change.op.should.deep.equal { i: "456", p: 3 }
change.metadata.user_id.should.equal @user_id change.metadata.user_id.should.equal @user_id
done() done()
describe "Loading changes from persistence layer", -> describe "Loading ranges from persistence layer", ->
before (done) -> before (done) ->
@project_id = DocUpdaterClient.randomId() @project_id = DocUpdaterClient.randomId()
@user_id = DocUpdaterClient.randomId() @user_id = DocUpdaterClient.randomId()
@ -72,7 +72,7 @@ describe "Track changes", ->
MockWebApi.insertDoc @project_id, @doc.id, { MockWebApi.insertDoc @project_id, @doc.id, {
lines: @doc.lines lines: @doc.lines
version: 0 version: 0
track_changes_entries: { ranges: {
changes: [{ changes: [{
op: { i: "123", p: 1 } op: { i: "123", p: 1 }
metadata: metadata:
@ -87,19 +87,19 @@ describe "Track changes", ->
throw error if error? throw error if error?
setTimeout done, 200 setTimeout done, 200
it "should have preloaded the existing changes", (done) -> it "should have preloaded the existing ranges", (done) ->
DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) => DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) =>
throw error if error? throw error if error?
{changes} = data.track_changes_entries {changes} = data.ranges
changes[0].op.should.deep.equal { i: "123", p: 1 } changes[0].op.should.deep.equal { i: "123", p: 1 }
changes[1].op.should.deep.equal { i: "456", p: 5 } changes[1].op.should.deep.equal { i: "456", p: 5 }
done() done()
it "should flush the changes to the persistence layer again", (done) -> it "should flush the ranges to the persistence layer again", (done) ->
DocUpdaterClient.flushDoc @project_id, @doc.id, (error) => DocUpdaterClient.flushDoc @project_id, @doc.id, (error) =>
throw error if error? throw error if error?
MockWebApi.getDocument @project_id, @doc.id, (error, doc) => MockWebApi.getDocument @project_id, @doc.id, (error, doc) =>
{changes} = doc.track_changes_entries {changes} = doc.ranges
changes[0].op.should.deep.equal { i: "123", p: 1 } changes[0].op.should.deep.equal { i: "123", p: 1 }
changes[1].op.should.deep.equal { i: "456", p: 5 } changes[1].op.should.deep.equal { i: "456", p: 5 }
done() done()

View file

@ -11,11 +11,11 @@ module.exports = MockWebApi =
doc.lines ?= [] doc.lines ?= []
@docs["#{project_id}:#{doc_id}"] = doc @docs["#{project_id}:#{doc_id}"] = doc
setDocument: (project_id, doc_id, lines, version, track_changes_entries, callback = (error) ->) -> setDocument: (project_id, doc_id, lines, version, ranges, callback = (error) ->) ->
doc = @docs["#{project_id}:#{doc_id}"] ||= {} doc = @docs["#{project_id}:#{doc_id}"] ||= {}
doc.lines = lines doc.lines = lines
doc.version = version doc.version = version
doc.track_changes_entries = track_changes_entries doc.ranges = ranges
callback null callback null
getDocument: (project_id, doc_id, callback = (error, doc) ->) -> getDocument: (project_id, doc_id, callback = (error, doc) ->) ->
@ -32,7 +32,7 @@ module.exports = MockWebApi =
res.send 404 res.send 404
app.post "/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) => app.post "/project/:project_id/doc/:doc_id", express.bodyParser(), (req, res, next) =>
MockWebApi.setDocument req.params.project_id, req.params.doc_id, req.body.lines, req.body.version, req.body.track_changes_entries, (error) -> MockWebApi.setDocument req.params.project_id, req.params.doc_id, req.body.lines, req.body.version, req.body.ranges, (error) ->
if error? if error?
res.send 500 res.send 500
else else

View file

@ -23,7 +23,7 @@ describe "DocumentManager", ->
@callback = sinon.stub() @callback = sinon.stub()
@lines = ["one", "two", "three"] @lines = ["one", "two", "three"]
@version = 42 @version = 42
@track_changes_entries = { comments: "mock", entries: "mock" } @ranges = { comments: "mock", entries: "mock" }
describe "flushAndDeleteDoc", -> describe "flushAndDeleteDoc", ->
describe "successfully", -> describe "successfully", ->
@ -49,7 +49,7 @@ describe "DocumentManager", ->
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
it "should flush to track changes", -> it "should flush to the history api", ->
@HistoryManager.flushDocChanges @HistoryManager.flushDocChanges
.calledWith(@project_id, @doc_id) .calledWith(@project_id, @doc_id)
.should.equal true .should.equal true
@ -57,7 +57,7 @@ describe "DocumentManager", ->
describe "flushDocIfLoaded", -> describe "flushDocIfLoaded", ->
describe "when the doc is in Redis", -> describe "when the doc is in Redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @track_changes_entries) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
@PersistenceManager.setDoc = sinon.stub().yields() @PersistenceManager.setDoc = sinon.stub().yields()
@DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback @DocumentManager.flushDocIfLoaded @project_id, @doc_id, @callback
@ -68,7 +68,7 @@ describe "DocumentManager", ->
it "should write the doc lines to the persistence layer", -> it "should write the doc lines to the persistence layer", ->
@PersistenceManager.setDoc @PersistenceManager.setDoc
.calledWith(@project_id, @doc_id, @lines, @version, @track_changes_entries) .calledWith(@project_id, @doc_id, @lines, @version, @ranges)
.should.equal true .should.equal true
it "should call the callback without error", -> it "should call the callback without error", ->
@ -102,7 +102,7 @@ describe "DocumentManager", ->
describe "getDocAndRecentOps", -> describe "getDocAndRecentOps", ->
describe "with a previous version specified", -> describe "with a previous version specified", ->
beforeEach -> beforeEach ->
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @track_changes_entries) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops) @RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback @DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback
@ -117,14 +117,14 @@ describe "DocumentManager", ->
.should.equal true .should.equal true
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, @ops, @track_changes_entries).should.equal true @callback.calledWith(null, @lines, @version, @ops, @ranges).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
describe "with no previous version specified", -> describe "with no previous version specified", ->
beforeEach -> beforeEach ->
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @track_changes_entries) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops) @RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback @DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback
@ -137,7 +137,7 @@ describe "DocumentManager", ->
@RedisManager.getPreviousDocOps.called.should.equal false @RedisManager.getPreviousDocOps.called.should.equal false
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, [], @track_changes_entries).should.equal true @callback.calledWith(null, @lines, @version, [], @ranges).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -145,7 +145,7 @@ describe "DocumentManager", ->
describe "getDoc", -> describe "getDoc", ->
describe "when the doc exists in Redis", -> describe "when the doc exists in Redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @track_changes_entries) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
@DocumentManager.getDoc @project_id, @doc_id, @callback @DocumentManager.getDoc @project_id, @doc_id, @callback
it "should get the doc from Redis", -> it "should get the doc from Redis", ->
@ -154,7 +154,7 @@ describe "DocumentManager", ->
.should.equal true .should.equal true
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, @track_changes_entries, true).should.equal true @callback.calledWith(null, @lines, @version, @ranges, true).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -162,7 +162,7 @@ describe "DocumentManager", ->
describe "when the doc does not exist in Redis", -> describe "when the doc does not exist in Redis", ->
beforeEach -> beforeEach ->
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null) @RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null)
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @track_changes_entries) @PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges)
@RedisManager.putDocInMemory = sinon.stub().yields() @RedisManager.putDocInMemory = sinon.stub().yields()
@DocumentManager.getDoc @project_id, @doc_id, @callback @DocumentManager.getDoc @project_id, @doc_id, @callback
@ -178,11 +178,11 @@ describe "DocumentManager", ->
it "should set the doc in Redis", -> it "should set the doc in Redis", ->
@RedisManager.putDocInMemory @RedisManager.putDocInMemory
.calledWith(@project_id, @doc_id, @lines, @version, @track_changes_entries) .calledWith(@project_id, @doc_id, @lines, @version, @ranges)
.should.equal true .should.equal true
it "should call the callback with the doc info", -> it "should call the callback with the doc info", ->
@callback.calledWith(null, @lines, @version, @track_changes_entries, false).should.equal true @callback.calledWith(null, @lines, @version, @ranges, false).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -192,7 +192,7 @@ describe "DocumentManager", ->
beforeEach -> beforeEach ->
@beforeLines = ["before", "lines"] @beforeLines = ["before", "lines"]
@afterLines = ["after", "lines"] @afterLines = ["after", "lines"]
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @track_changes_entries, true) @DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @ranges, true)
@DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops) @DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops)
@UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null) @UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null)
@DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2) @DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2)

View file

@ -22,7 +22,7 @@ describe "HttpController.getDoc", ->
@ops = ["mock-op-1", "mock-op-2"] @ops = ["mock-op-1", "mock-op-2"]
@version = 42 @version = 42
@fromVersion = 42 @fromVersion = 42
@track_changes_entries = { changes: "mock", comments: "mock" } @ranges = { changes: "mock", comments: "mock" }
@res = @res =
send: sinon.stub() send: sinon.stub()
@req = @req =
@ -33,7 +33,7 @@ describe "HttpController.getDoc", ->
describe "when the document exists and no recent ops are requested", -> describe "when the document exists and no recent ops are requested", ->
beforeEach -> beforeEach ->
@DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, @lines, @version, [], @track_changes_entries) @DocumentManager.getDocAndRecentOpsWithLock = sinon.stub().callsArgWith(3, null, @lines, @version, [], @ranges)
@HttpController.getDoc(@req, @res, @next) @HttpController.getDoc(@req, @res, @next)
it "should get the doc", -> it "should get the doc", ->
@ -48,7 +48,7 @@ describe "HttpController.getDoc", ->
lines: @lines lines: @lines
version: @version version: @version
ops: [] ops: []
track_changes_entries: @track_changes_entries ranges: @ranges
})) }))
.should.equal true .should.equal true

View file

@ -19,7 +19,7 @@ describe "PersistenceManager", ->
@lines = ["one", "two", "three"] @lines = ["one", "two", "three"]
@version = 42 @version = 42
@callback = sinon.stub() @callback = sinon.stub()
@track_changes_entries = { comments: "mock", entries: "mock" } @ranges = { comments: "mock", entries: "mock" }
@Settings.apis = @Settings.apis =
web: web:
url: @url = "www.example.com" url: @url = "www.example.com"
@ -33,7 +33,7 @@ describe "PersistenceManager", ->
@request.callsArgWith(1, null, {statusCode: 200}, JSON.stringify({ @request.callsArgWith(1, null, {statusCode: 200}, JSON.stringify({
lines: @lines, lines: @lines,
version: @version, version: @version,
track_changes_entries: @track_changes_entries ranges: @ranges
})) }))
@PersistenceManager.getDoc(@project_id, @doc_id, @callback) @PersistenceManager.getDoc(@project_id, @doc_id, @callback)
@ -53,8 +53,8 @@ describe "PersistenceManager", ->
}) })
.should.equal true .should.equal true
it "should call the callback with the doc lines, version and track changes state", -> it "should call the callback with the doc lines, version and ranges", ->
@callback.calledWith(null, @lines, @version, @track_changes_entries).should.equal true @callback.calledWith(null, @lines, @version, @ranges).should.equal true
it "should time the execution", -> it "should time the execution", ->
@Metrics.Timer::done.called.should.equal true @Metrics.Timer::done.called.should.equal true
@ -112,7 +112,7 @@ describe "PersistenceManager", ->
describe "with a successful response from the web api", -> describe "with a successful response from the web api", ->
beforeEach -> beforeEach ->
@request.callsArgWith(1, null, {statusCode: 200}) @request.callsArgWith(1, null, {statusCode: 200})
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @track_changes_entries, @callback) @PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
it "should call the web api", -> it "should call the web api", ->
@request @request
@ -121,7 +121,7 @@ describe "PersistenceManager", ->
json: json:
lines: @lines lines: @lines
version: @version version: @version
track_changes_entries: @track_changes_entries ranges: @ranges
method: "POST" method: "POST"
auth: auth:
user: @user user: @user
@ -141,7 +141,7 @@ describe "PersistenceManager", ->
describe "when request returns an error", -> describe "when request returns an error", ->
beforeEach -> beforeEach ->
@request.callsArgWith(1, @error = new Error("oops"), null, null) @request.callsArgWith(1, @error = new Error("oops"), null, null)
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @track_changes_entries, @callback) @PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
it "should return the error", -> it "should return the error", ->
@callback.calledWith(@error).should.equal true @callback.calledWith(@error).should.equal true
@ -152,7 +152,7 @@ describe "PersistenceManager", ->
describe "when the request returns 404", -> describe "when the request returns 404", ->
beforeEach -> beforeEach ->
@request.callsArgWith(1, null, {statusCode: 404}, "") @request.callsArgWith(1, null, {statusCode: 404}, "")
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @track_changes_entries, @callback) @PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
it "should return a NotFoundError", -> it "should return a NotFoundError", ->
@callback.calledWith(new Errors.NotFoundError("not found")).should.equal true @callback.calledWith(new Errors.NotFoundError("not found")).should.equal true
@ -163,7 +163,7 @@ describe "PersistenceManager", ->
describe "when the request returns an error status code", -> describe "when the request returns an error status code", ->
beforeEach -> beforeEach ->
@request.callsArgWith(1, null, {statusCode: 500}, "") @request.callsArgWith(1, null, {statusCode: 500}, "")
@PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @track_changes_entries, @callback) @PersistenceManager.setDoc(@project_id, @doc_id, @lines, @version, @ranges, @callback)
it "should return an error", -> it "should return an error", ->
@callback.calledWith(new Error("web api error")).should.equal true @callback.calledWith(new Error("web api error")).should.equal true

View file

@ -22,7 +22,7 @@ describe "RedisManager", ->
projectKey: ({doc_id}) -> "ProjectId:#{doc_id}" projectKey: ({doc_id}) -> "ProjectId:#{doc_id}"
pendingUpdates: ({doc_id}) -> "PendingUpdates:#{doc_id}" pendingUpdates: ({doc_id}) -> "PendingUpdates:#{doc_id}"
docsInProject: ({project_id}) -> "DocsIn:#{project_id}" docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
trackChangesEntries: ({doc_id}) -> "TrackChangesEntries:#{doc_id}" ranges: ({doc_id}) -> "Ranges:#{doc_id}"
"logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() } "logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub(), warn: sinon.stub() }
"./Metrics": @metrics = "./Metrics": @metrics =
inc: sinon.stub() inc: sinon.stub()
@ -38,11 +38,10 @@ describe "RedisManager", ->
@lines = ["one", "two", "three"] @lines = ["one", "two", "three"]
@jsonlines = JSON.stringify @lines @jsonlines = JSON.stringify @lines
@version = 42 @version = 42
@track_changes_on = true @ranges = { comments: "mock", entries: "mock" }
@track_changes_entries = { comments: "mock", entries: "mock" } @json_ranges = JSON.stringify @ranges
@json_track_changes_entries = JSON.stringify @track_changes_entries
@rclient.get = sinon.stub() @rclient.get = sinon.stub()
@rclient.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @project_id, @json_track_changes_entries]) @rclient.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @project_id, @json_ranges])
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@ -58,20 +57,20 @@ describe "RedisManager", ->
.calledWith("DocVersion:#{@doc_id}") .calledWith("DocVersion:#{@doc_id}")
.should.equal true .should.equal true
it "should get the track changes entries", -> it "should get the ranges", ->
@rclient.get @rclient.get
.calledWith("TrackChangesEntries:#{@doc_id}") .calledWith("Ranges:#{@doc_id}")
.should.equal true .should.equal true
it 'should return the document', -> it 'should return the document', ->
@callback @callback
.calledWith(null, @lines, @version, @track_changes_entries) .calledWith(null, @lines, @version, @ranges)
.should.equal true .should.equal true
describe "getDoc with an invalid project id", -> describe "getDoc with an invalid project id", ->
beforeEach -> beforeEach ->
@another_project_id = "project-id-456" @another_project_id = "project-id-456"
@rclient.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @another_project_id, @json_track_changes_entries]) @rclient.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @another_project_id, @json_ranges])
@RedisManager.getDoc @project_id, @doc_id, @callback @RedisManager.getDoc @project_id, @doc_id, @callback
it 'should return an error', -> it 'should return an error', ->
@ -169,19 +168,20 @@ describe "RedisManager", ->
@rclient.rpush = sinon.stub() @rclient.rpush = sinon.stub()
@rclient.expire = sinon.stub() @rclient.expire = sinon.stub()
@rclient.ltrim = sinon.stub() @rclient.ltrim = sinon.stub()
@rclient.del = sinon.stub()
@RedisManager.getDocVersion = sinon.stub() @RedisManager.getDocVersion = sinon.stub()
@lines = ["one", "two", "three"] @lines = ["one", "two", "three"]
@ops = [{ op: [{ i: "foo", p: 4 }] },{ op: [{ i: "bar", p: 8 }] }] @ops = [{ op: [{ i: "foo", p: 4 }] },{ op: [{ i: "bar", p: 8 }] }]
@version = 42 @version = 42
@track_changes_entries = { comments: "mock", entries: "mock" } @ranges = { comments: "mock", entries: "mock" }
@rclient.exec = sinon.stub().callsArg(0) @rclient.exec = sinon.stub().callsArg(0)
describe "with a consistent version", -> describe "with a consistent version", ->
beforeEach -> beforeEach ->
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length) @RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
@RedisManager.updateDocument @doc_id, @lines, @version, @ops, @track_changes_entries, @callback @RedisManager.updateDocument @doc_id, @lines, @version, @ops, @ranges, @callback
it "should get the current doc version to check for consistency", -> it "should get the current doc version to check for consistency", ->
@RedisManager.getDocVersion @RedisManager.getDocVersion
@ -198,9 +198,9 @@ describe "RedisManager", ->
.calledWith("DocVersion:#{@doc_id}", @version) .calledWith("DocVersion:#{@doc_id}", @version)
.should.equal true .should.equal true
it "should set the track changes entries", -> it "should set the ranges", ->
@rclient.set @rclient.set
.calledWith("TrackChangesEntries:#{@doc_id}", JSON.stringify(@track_changes_entries)) .calledWith("Ranges:#{@doc_id}", JSON.stringify(@ranges))
.should.equal true .should.equal true
it "should push the doc op into the doc ops list", -> it "should push the doc op into the doc ops list", ->
@ -224,7 +224,7 @@ describe "RedisManager", ->
describe "with an inconsistent version", -> describe "with an inconsistent version", ->
beforeEach -> beforeEach ->
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length - 1) @RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length - 1)
@RedisManager.updateDocument @doc_id, @lines, @version, @ops, @track_changes_entries, @callback @RedisManager.updateDocument @doc_id, @lines, @version, @ops, @ranges, @callback
it "should not call multi.exec", -> it "should not call multi.exec", ->
@rclient.exec.called.should.equal false @rclient.exec.called.should.equal false
@ -237,7 +237,7 @@ describe "RedisManager", ->
describe "with no updates", -> describe "with no updates", ->
beforeEach -> beforeEach ->
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version) @RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version)
@RedisManager.updateDocument @doc_id, @lines, @version, [], @track_changes_entries, @callback @RedisManager.updateDocument @doc_id, @lines, @version, [], @ranges, @callback
it "should not do an rpush", -> it "should not do an rpush", ->
@rclient.rpush @rclient.rpush
@ -248,42 +248,75 @@ describe "RedisManager", ->
@rclient.set @rclient.set
.calledWith("doclines:#{@doc_id}", JSON.stringify(@lines)) .calledWith("doclines:#{@doc_id}", JSON.stringify(@lines))
.should.equal true .should.equal true
describe "with empty ranges", ->
beforeEach ->
@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
@RedisManager.updateDocument @doc_id, @lines, @version, @ops, {}, @callback
it "should not set the ranges", ->
@rclient.set
.calledWith("Ranges:#{@doc_id}", JSON.stringify(@ranges))
.should.equal false
it "should delete the ranges key", ->
@rclient.del
.calledWith("Ranges:#{@doc_id}")
.should.equal true
describe "putDocInMemory", -> describe "putDocInMemory", ->
beforeEach (done) -> beforeEach ->
@rclient.set = sinon.stub() @rclient.set = sinon.stub()
@rclient.sadd = sinon.stub().yields() @rclient.sadd = sinon.stub().yields()
@rclient.del = sinon.stub()
@rclient.exec.yields() @rclient.exec.yields()
@lines = ["one", "two", "three"] @lines = ["one", "two", "three"]
@version = 42 @version = 42
@track_changes_entries = { comments: "mock", entries: "mock" } @ranges = { comments: "mock", entries: "mock" }
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @track_changes_entries, done
it "should set the lines", -> describe "with non-empty ranges", ->
@rclient.set beforeEach (done) ->
.calledWith("doclines:#{@doc_id}", JSON.stringify @lines) @RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, done
.should.equal true
it "should set the version", ->
@rclient.set
.calledWith("DocVersion:#{@doc_id}", @version)
.should.equal true
it "should set the track changes entries", -> it "should set the lines", ->
@rclient.set @rclient.set
.calledWith("TrackChangesEntries:#{@doc_id}", JSON.stringify(@track_changes_entries)) .calledWith("doclines:#{@doc_id}", JSON.stringify @lines)
.should.equal true .should.equal true
it "should set the project_id for the doc", -> it "should set the version", ->
@rclient.set @rclient.set
.calledWith("ProjectId:#{@doc_id}", @project_id) .calledWith("DocVersion:#{@doc_id}", @version)
.should.equal true .should.equal true
it "should add the doc_id to the project set", -> it "should set the ranges", ->
@rclient.sadd @rclient.set
.calledWith("DocsIn:#{@project_id}", @doc_id) .calledWith("Ranges:#{@doc_id}", JSON.stringify(@ranges))
.should.equal true .should.equal true
it "should set the project_id for the doc", ->
@rclient.set
.calledWith("ProjectId:#{@doc_id}", @project_id)
.should.equal true
it "should add the doc_id to the project set", ->
@rclient.sadd
.calledWith("DocsIn:#{@project_id}", @doc_id)
.should.equal true
describe "with empty ranges", ->
beforeEach (done) ->
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, {}, done
it "should delete the ranges key", ->
@rclient.del
.calledWith("Ranges:#{@doc_id}")
.should.equal true
it "should not set the ranges", ->
@rclient.set
.calledWith("Ranges:#{@doc_id}", JSON.stringify(@ranges))
.should.equal false
describe "removeDocFromMemory", -> describe "removeDocFromMemory", ->
beforeEach (done) -> beforeEach (done) ->
@rclient.del = sinon.stub() @rclient.del = sinon.stub()

View file

@ -21,7 +21,7 @@ describe "UpdateManager", ->
done: sinon.stub() done: sinon.stub()
"settings-sharelatex": Settings = {} "settings-sharelatex": Settings = {}
"./DocumentManager": @DocumentManager = {} "./DocumentManager": @DocumentManager = {}
"./TrackChangesManager": @TrackChangesManager = {} "./RangesManager": @RangesManager = {}
describe "processOutstandingUpdates", -> describe "processOutstandingUpdates", ->
beforeEach -> beforeEach ->
@ -158,11 +158,11 @@ describe "UpdateManager", ->
@updatedDocLines = ["updated", "lines"] @updatedDocLines = ["updated", "lines"]
@version = 34 @version = 34
@lines = ["original", "lines"] @lines = ["original", "lines"]
@track_changes_entries = { entries: "mock", comments: "mock" } @ranges = { entries: "mock", comments: "mock" }
@updated_track_changes_entries = { entries: "updated", comments: "updated" } @updated_ranges = { entries: "updated", comments: "updated" }
@appliedOps = ["mock-applied-ops"] @appliedOps = ["mock-applied-ops"]
@DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @track_changes_entries) @DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges)
@TrackChangesManager.applyUpdate = sinon.stub().yields(null, @updated_track_changes_entries) @RangesManager.applyUpdate = sinon.stub().yields(null, @updated_ranges)
@ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, @updatedDocLines, @version, @appliedOps) @ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, @updatedDocLines, @version, @appliedOps)
@RedisManager.updateDocument = sinon.stub().yields() @RedisManager.updateDocument = sinon.stub().yields()
@HistoryManager.pushUncompressedHistoryOps = sinon.stub().callsArg(3) @HistoryManager.pushUncompressedHistoryOps = sinon.stub().callsArg(3)
@ -176,17 +176,17 @@ describe "UpdateManager", ->
.calledWith(@project_id, @doc_id, @update, @lines, @version) .calledWith(@project_id, @doc_id, @update, @lines, @version)
.should.equal true .should.equal true
it "should update the track changes entries", -> it "should update the ranges", ->
@TrackChangesManager.applyUpdate @RangesManager.applyUpdate
.calledWith(@project_id, @doc_id, @track_changes_entries, @appliedOps) .calledWith(@project_id, @doc_id, @ranges, @appliedOps)
.should.equal true .should.equal true
it "should save the document", -> it "should save the document", ->
@RedisManager.updateDocument @RedisManager.updateDocument
.calledWith(@doc_id, @updatedDocLines, @version, @appliedOps, @updated_track_changes_entries) .calledWith(@doc_id, @updatedDocLines, @version, @appliedOps, @updated_ranges)
.should.equal true .should.equal true
it "should push the applied ops into the track changes queue", -> it "should push the applied ops into the history queue", ->
@HistoryManager.pushUncompressedHistoryOps @HistoryManager.pushUncompressedHistoryOps
.calledWith(@project_id, @doc_id, @appliedOps) .calledWith(@project_id, @doc_id, @appliedOps)
.should.equal true .should.equal true