2014-02-12 10:40:42 +00:00
|
|
|
RedisManager = require "./RedisManager"
|
2018-03-09 14:14:14 +00:00
|
|
|
ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
|
2014-02-12 10:40:42 +00:00
|
|
|
DocumentManager = require "./DocumentManager"
|
2018-01-23 11:11:15 +00:00
|
|
|
HistoryManager = require "./HistoryManager"
|
2014-02-12 10:40:42 +00:00
|
|
|
async = require "async"
|
|
|
|
logger = require "logger-sharelatex"
|
|
|
|
Metrics = require "./Metrics"
|
2017-08-07 13:43:28 +00:00
|
|
|
Errors = require "./Errors"
|
2014-02-12 10:40:42 +00:00
|
|
|
|
|
|
|
module.exports = ProjectManager =
|
|
|
|
flushProjectWithLocks: (project_id, _callback = (error) ->) ->
|
|
|
|
timer = new Metrics.Timer("projectManager.flushProjectWithLocks")
|
|
|
|
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.flushDocIfLoadedWithLock project_id, doc_id, (error) ->
|
2017-12-19 12:27:57 +00:00
|
|
|
if error? and error instanceof Errors.NotFoundError
|
2017-12-20 13:26:57 +00:00
|
|
|
logger.warn err: error, project_id: project_id, doc_id: doc_id, "found deleted doc when flushing"
|
2017-12-19 12:27:57 +00:00
|
|
|
callback()
|
|
|
|
else if error?
|
2014-02-12 10:40:42 +00:00
|
|
|
logger.error err: error, project_id: project_id, doc_id: doc_id, "error flushing doc"
|
|
|
|
errors.push(error)
|
2017-12-19 12:27:57 +00:00
|
|
|
callback()
|
|
|
|
else
|
|
|
|
callback()
|
2014-02-12 10:40:42 +00:00
|
|
|
|
|
|
|
logger.log project_id: project_id, doc_ids: doc_ids, "flushing docs"
|
|
|
|
async.series jobs, () ->
|
|
|
|
if errors.length > 0
|
|
|
|
callback new Error("Errors flushing docs. See log for details")
|
|
|
|
else
|
|
|
|
callback(null)
|
|
|
|
|
2019-06-03 09:01:10 +00:00
|
|
|
flushAndDeleteProjectWithLocks: (project_id, options, _callback = (error) ->) ->
|
2014-02-12 10:40:42 +00:00
|
|
|
timer = new Metrics.Timer("projectManager.flushAndDeleteProjectWithLocks")
|
|
|
|
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.flushAndDeleteDocWithLock project_id, doc_id, (error) ->
|
|
|
|
if error?
|
|
|
|
logger.error err: error, project_id: project_id, doc_id: doc_id, "error deleting doc"
|
|
|
|
errors.push(error)
|
|
|
|
callback()
|
|
|
|
|
|
|
|
logger.log project_id: project_id, doc_ids: doc_ids, "deleting docs"
|
|
|
|
async.series jobs, () ->
|
2018-10-30 11:56:27 +00:00
|
|
|
# When deleting the project here we want to ensure that project
|
|
|
|
# history is completely flushed because the project may be
|
|
|
|
# deleted in web after this call completes, and so further
|
|
|
|
# attempts to flush would fail after that.
|
2019-06-03 09:01:10 +00:00
|
|
|
HistoryManager.flushProjectChanges project_id, options, (error) ->
|
2018-10-30 11:56:27 +00:00
|
|
|
if errors.length > 0
|
|
|
|
callback new Error("Errors deleting docs. See log for details")
|
|
|
|
else if error?
|
|
|
|
callback(error)
|
|
|
|
else
|
|
|
|
callback(null)
|
2017-07-28 15:34:32 +00:00
|
|
|
|
2019-09-25 15:42:49 +00:00
|
|
|
queueFlushAndDeleteProject: (project_id, callback = (error) ->) ->
|
|
|
|
RedisManager.queueFlushAndDeleteProject project_id, (error) ->
|
|
|
|
if error?
|
|
|
|
logger.error {project_id: project_id, error:error}, "error adding project to flush and delete queue"
|
|
|
|
return callback(error)
|
|
|
|
Metrics.inc "queued-delete"
|
|
|
|
callback()
|
|
|
|
|
|
|
|
getProjectDocsTimestamps: (project_id, callback = (error) ->) ->
|
|
|
|
RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
|
|
|
|
return callback(error) if error?
|
2019-09-26 13:59:03 +00:00
|
|
|
return callback(null, []) if !doc_ids?.length
|
2019-09-25 15:42:49 +00:00
|
|
|
RedisManager.getDocTimestamps doc_ids, (error, timestamps) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
callback(null, timestamps)
|
|
|
|
|
2017-10-11 14:29:57 +00:00
|
|
|
getProjectDocsAndFlushIfOld: (project_id, projectStateHash, excludeVersions = {}, _callback = (error, docs) ->) ->
|
|
|
|
timer = new Metrics.Timer("projectManager.getProjectDocsAndFlushIfOld")
|
2017-07-28 15:34:32 +00:00
|
|
|
callback = (args...) ->
|
|
|
|
timer.done()
|
|
|
|
_callback(args...)
|
|
|
|
|
2017-08-07 13:43:28 +00:00
|
|
|
RedisManager.checkOrSetProjectState project_id, projectStateHash, (error, projectStateChanged) ->
|
2017-09-07 13:12:48 +00:00
|
|
|
if error?
|
2017-10-11 14:29:57 +00:00
|
|
|
logger.error err: error, project_id: project_id, "error getting/setting project state in getProjectDocsAndFlushIfOld"
|
2017-09-07 13:12:48 +00:00
|
|
|
return callback(error)
|
2017-08-07 13:43:28 +00:00
|
|
|
# we can't return docs if project structure has changed
|
2017-10-06 11:23:23 +00:00
|
|
|
if projectStateChanged
|
|
|
|
return callback Errors.ProjectStateChangedError("project state changed")
|
2017-08-07 13:43:28 +00:00
|
|
|
# project structure hasn't changed, return doc content from redis
|
|
|
|
RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
|
2017-09-07 13:12:48 +00:00
|
|
|
if error?
|
|
|
|
logger.error err: error, project_id: project_id, "error getting doc ids in getProjectDocs"
|
|
|
|
return callback(error)
|
2017-08-07 13:43:28 +00:00
|
|
|
jobs = []
|
|
|
|
for doc_id in doc_ids or []
|
|
|
|
do (doc_id) ->
|
|
|
|
jobs.push (cb) ->
|
2017-10-06 11:23:23 +00:00
|
|
|
# get the doc lines from redis
|
|
|
|
DocumentManager.getDocAndFlushIfOldWithLock project_id, doc_id, (err, lines, version) ->
|
|
|
|
if err?
|
2017-10-11 14:29:57 +00:00
|
|
|
logger.error err:err, project_id: project_id, doc_id: doc_id, "error getting project doc lines in getProjectDocsAndFlushIfOld"
|
2017-10-06 11:23:23 +00:00
|
|
|
return cb(err)
|
|
|
|
doc = {_id:doc_id, lines:lines, v:version} # create a doc object to return
|
|
|
|
cb(null, doc)
|
|
|
|
async.series jobs, (error, docs) ->
|
2017-08-07 13:43:28 +00:00
|
|
|
return callback(error) if error?
|
|
|
|
callback(null, docs)
|
2017-09-08 14:50:26 +00:00
|
|
|
|
|
|
|
clearProjectState: (project_id, callback = (error) ->) ->
|
|
|
|
RedisManager.clearProjectState project_id, callback
|
2017-11-01 19:16:49 +00:00
|
|
|
|
2018-04-23 11:08:04 +00:00
|
|
|
updateProjectWithLocks: (project_id, projectHistoryId, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) ->
|
2017-11-01 19:16:49 +00:00
|
|
|
timer = new Metrics.Timer("projectManager.updateProject")
|
|
|
|
callback = (args...) ->
|
|
|
|
timer.done()
|
|
|
|
_callback(args...)
|
|
|
|
|
2018-03-05 12:14:47 +00:00
|
|
|
project_version = version
|
|
|
|
project_subversion = 0 # project versions can have multiple operations
|
|
|
|
|
2018-01-26 14:41:10 +00:00
|
|
|
project_ops_length = 0
|
|
|
|
|
2018-03-16 10:54:12 +00:00
|
|
|
handleDocUpdate = (projectUpdate, cb) ->
|
|
|
|
doc_id = projectUpdate.id
|
|
|
|
projectUpdate.version = "#{project_version}.#{project_subversion++}"
|
|
|
|
if projectUpdate.docLines?
|
2018-04-23 11:08:04 +00:00
|
|
|
ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'doc', doc_id, user_id, projectUpdate, (error, count) ->
|
2018-01-26 14:41:10 +00:00
|
|
|
project_ops_length = count
|
|
|
|
cb(error)
|
2017-11-10 14:54:56 +00:00
|
|
|
else
|
2018-04-23 11:08:04 +00:00
|
|
|
DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, projectHistoryId, (error, count) ->
|
2018-01-26 14:41:10 +00:00
|
|
|
project_ops_length = count
|
|
|
|
cb(error)
|
2017-11-01 19:16:49 +00:00
|
|
|
|
2018-03-16 10:54:12 +00:00
|
|
|
handleFileUpdate = (projectUpdate, cb) ->
|
|
|
|
file_id = projectUpdate.id
|
|
|
|
projectUpdate.version = "#{project_version}.#{project_subversion++}"
|
|
|
|
if projectUpdate.url?
|
2018-04-23 11:08:04 +00:00
|
|
|
ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
|
2018-01-26 14:41:10 +00:00
|
|
|
project_ops_length = count
|
|
|
|
cb(error)
|
2017-11-10 14:54:56 +00:00
|
|
|
else
|
2018-04-23 11:08:04 +00:00
|
|
|
ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
|
2018-01-26 14:41:10 +00:00
|
|
|
project_ops_length = count
|
|
|
|
cb(error)
|
2017-11-06 16:14:27 +00:00
|
|
|
|
2018-03-07 17:06:42 +00:00
|
|
|
async.eachSeries docUpdates, handleDocUpdate, (error) ->
|
2017-11-06 16:14:27 +00:00
|
|
|
return callback(error) if error?
|
2018-03-07 17:06:42 +00:00
|
|
|
async.eachSeries fileUpdates, handleFileUpdate, (error) ->
|
2018-01-23 11:11:15 +00:00
|
|
|
return callback(error) if error?
|
2018-01-24 11:37:28 +00:00
|
|
|
if HistoryManager.shouldFlushHistoryOps(project_ops_length, docUpdates.length + fileUpdates.length, HistoryManager.FLUSH_PROJECT_EVERY_N_OPS)
|
|
|
|
HistoryManager.flushProjectChangesAsync project_id
|
|
|
|
callback()
|