switch to single get/set method for getProjectDocs

if project state hasn't changed, return the docs. Otherwise set the hash
and return a 409 Conflict response.
This commit is contained in:
Brian Gough 2017-08-07 14:43:28 +01:00
parent f5f516a910
commit 9f3ec72f81
4 changed files with 57 additions and 27 deletions

View file

@ -12,7 +12,14 @@ OpRangeNotAvailableError = (message) ->
return error return error
OpRangeNotAvailableError.prototype.__proto__ = Error.prototype OpRangeNotAvailableError.prototype.__proto__ = Error.prototype
ProjectStateChangedError = (message) ->
error = new Error(message)
error.name = "ProjectStateChangedError"
error.__proto__ = ProjectStateChangedError.prototype
return error
ProjectStateChangedError.prototype.__proto__ = Error.prototype
module.exports = Errors = module.exports = Errors =
NotFoundError: NotFoundError NotFoundError: NotFoundError
OpRangeNotAvailableError: OpRangeNotAvailableError OpRangeNotAvailableError: OpRangeNotAvailableError
ProjectStateChangedError: ProjectStateChangedError

View file

@ -39,6 +39,7 @@ module.exports = HttpController =
getProjectDocs: (req, res, next = (error) ->) -> getProjectDocs: (req, res, next = (error) ->) ->
project_id = req.params.project_id project_id = req.params.project_id
projectStateHash = req.query?.state
# exclude is string of existing docs "id:version,id:version,..." # exclude is string of existing docs "id:version,id:version,..."
excludeItems = req.query?.exclude?.split(',') or [] excludeItems = req.query?.exclude?.split(',') or []
logger.log project_id: project_id, exclude: excludeItems, "getting docs via http" logger.log project_id: project_id, exclude: excludeItems, "getting docs via http"
@ -47,12 +48,16 @@ module.exports = HttpController =
for item in excludeItems for item in excludeItems
[id,version] = item?.split(':') [id,version] = item?.split(':')
excludeVersions[id] = version excludeVersions[id] = version
logger.log {project_id: project_id, excludeVersions: excludeVersions}, "excluding versions" logger.log {project_id: project_id, projectStateHash: projectStateHash, excludeVersions: excludeVersions}, "excluding versions"
ProjectManager.getProjectDocs project_id, excludeVersions, (error, result) -> ProjectManager.getProjectDocs project_id, projectStateHash, excludeVersions, (error, result) ->
timer.done() timer.done()
return next(error) if error? if error instanceof Errors.ProjectStateChangedError
logger.log project_id: project_id, result: ("#{doc._id}:#{doc.rev}" for doc in result), "got docs via http" res.send 409 # conflict
res.send result else if error?
return next(error)
else
logger.log project_id: project_id, result: ("#{doc._id}:#{doc.rev}" for doc in result), "got docs via http"
res.send result
setDoc: (req, res, next = (error) ->) -> setDoc: (req, res, next = (error) ->) ->
doc_id = req.params.doc_id doc_id = req.params.doc_id

View file

@ -3,6 +3,7 @@ DocumentManager = require "./DocumentManager"
async = require "async" async = require "async"
logger = require "logger-sharelatex" logger = require "logger-sharelatex"
Metrics = require "./Metrics" Metrics = require "./Metrics"
Errors = require "./Errors"
module.exports = ProjectManager = module.exports = ProjectManager =
flushProjectWithLocks: (project_id, _callback = (error) ->) -> flushProjectWithLocks: (project_id, _callback = (error) ->) ->
@ -57,29 +58,34 @@ module.exports = ProjectManager =
else else
callback(null) callback(null)
getProjectDocs: (project_id, excludeVersions = {}, _callback = (error) ->) -> getProjectDocs: (project_id, projectStateHash, excludeVersions = {}, _callback = (error) ->) ->
timer = new Metrics.Timer("projectManager.getProjectDocs") timer = new Metrics.Timer("projectManager.getProjectDocs")
callback = (args...) -> callback = (args...) ->
timer.done() timer.done()
_callback(args...) _callback(args...)
RedisManager.getDocIdsInProject project_id, (error, doc_ids) -> RedisManager.checkOrSetProjectState project_id, projectStateHash, (error, projectStateChanged) ->
return callback(error) if error? return callback(error) if error?
jobs = [] # we can't return docs if project structure has changed
docs = [] return callback Errors.ProjectStateChangedError("project state changed") if projectStateChanged
for doc_id in doc_ids or [] # project structure hasn't changed, return doc content from redis
do (doc_id) -> RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
jobs.push (cb) ->
# check the doc version first
RedisManager.getDocVersion doc_id, (error, version) ->
return cb(error) if error?
# skip getting the doc if we already have that version
return cb() if version is excludeVersions[doc_id]
# otherwise get the doc lines from redis
RedisManager.getDocLines doc_id, (error, lines) ->
return cb(error) if error?
docs.push {_id: doc_id, lines: lines, rev: version}
cb()
async.series jobs, (error) ->
return callback(error) if error? return callback(error) if error?
callback(null, docs) jobs = []
docs = []
for doc_id in doc_ids or []
do (doc_id) ->
jobs.push (cb) ->
# check the doc version first
RedisManager.getDocVersion doc_id, (error, version) ->
return cb(error) if error?
# skip getting the doc if we already have that version
return cb() if version is excludeVersions[doc_id]
# otherwise get the doc lines from redis
RedisManager.getDocLines doc_id, (error, lines) ->
return cb(error) if error?
docs.push {_id: doc_id, lines: lines, rev: version}
cb()
async.series jobs, (error) ->
return callback(error) if error?
callback(null, docs)

View file

@ -90,10 +90,22 @@ module.exports = RedisManager =
return callback(error) if error? return callback(error) if error?
multi = rclient.multi() multi = rclient.multi()
multi.srem keys.docsInProject(project_id:project_id), doc_id multi.srem keys.docsInProject(project_id:project_id), doc_id
if keys.clsiState? if keys.projectState?
multi.del keys.clsiState(project_id:project_id) multi.del keys.projectState(project_id:project_id)
multi.exec callback multi.exec callback
checkOrSetProjectState: (project_id, newState, callback = (error, stateChanged) ->) ->
if keys.projectState?
multi = rclient.multi()
multi.getset keys.projectState(project_id:project_id), newState
multi.expire keys.projectState(project_id:project_id), 30 * minutes
multi.exec (error, response) ->
return callback(error) if error?
logger.log project_id: project_id, newState:newState, oldState: response[0], "checking project state"
callback(null, response[0] isnt newState)
else
callback(null,true)
getDoc : (project_id, doc_id, callback = (error, lines, version, ranges) ->)-> 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()