RedisManager = require "./RedisManager" ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager" DocumentManager = require "./DocumentManager" HistoryManager = require "./HistoryManager" async = require "async" logger = require "logger-sharelatex" Metrics = require "./Metrics" Errors = require "./Errors" 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) -> if error? and error instanceof Errors.NotFoundError logger.warn err: error, project_id: project_id, doc_id: doc_id, "found deleted doc when flushing" callback() else if error? logger.error err: error, project_id: project_id, doc_id: doc_id, "error flushing doc" errors.push(error) callback() else callback() 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) flushAndDeleteProjectWithLocks: (project_id, _callback = (error) ->) -> 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, () -> # There is no harm in flushing project history if the previous call # failed and sometimes it is required HistoryManager.flushProjectChangesAsync project_id if errors.length > 0 callback new Error("Errors deleting docs. See log for details") else callback(null) getProjectDocsAndFlushIfOld: (project_id, projectStateHash, excludeVersions = {}, _callback = (error, docs) ->) -> timer = new Metrics.Timer("projectManager.getProjectDocsAndFlushIfOld") callback = (args...) -> timer.done() _callback(args...) RedisManager.checkOrSetProjectState project_id, projectStateHash, (error, projectStateChanged) -> if error? logger.error err: error, project_id: project_id, "error getting/setting project state in getProjectDocsAndFlushIfOld" return callback(error) # we can't return docs if project structure has changed if projectStateChanged return callback Errors.ProjectStateChangedError("project state changed") # project structure hasn't changed, return doc content from redis RedisManager.getDocIdsInProject project_id, (error, doc_ids) -> if error? logger.error err: error, project_id: project_id, "error getting doc ids in getProjectDocs" return callback(error) jobs = [] for doc_id in doc_ids or [] do (doc_id) -> jobs.push (cb) -> # get the doc lines from redis DocumentManager.getDocAndFlushIfOldWithLock project_id, doc_id, (err, lines, version) -> if err? logger.error err:err, project_id: project_id, doc_id: doc_id, "error getting project doc lines in getProjectDocsAndFlushIfOld" 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) -> return callback(error) if error? callback(null, docs) clearProjectState: (project_id, callback = (error) ->) -> RedisManager.clearProjectState project_id, callback updateProjectWithLocks: (project_id, projectHistoryId, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) -> timer = new Metrics.Timer("projectManager.updateProject") callback = (args...) -> timer.done() _callback(args...) project_version = version project_subversion = 0 # project versions can have multiple operations project_ops_length = 0 handleDocUpdate = (projectUpdate, cb) -> doc_id = projectUpdate.id projectUpdate.version = "#{project_version}.#{project_subversion++}" if projectUpdate.docLines? ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'doc', doc_id, user_id, projectUpdate, (error, count) -> project_ops_length = count cb(error) else DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, projectHistoryId, (error, count) -> project_ops_length = count cb(error) handleFileUpdate = (projectUpdate, cb) -> file_id = projectUpdate.id projectUpdate.version = "#{project_version}.#{project_subversion++}" if projectUpdate.url? ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) -> project_ops_length = count cb(error) else ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) -> project_ops_length = count cb(error) async.eachSeries docUpdates, handleDocUpdate, (error) -> return callback(error) if error? async.eachSeries fileUpdates, handleFileUpdate, (error) -> return callback(error) if error? if HistoryManager.shouldFlushHistoryOps(project_ops_length, docUpdates.length + fileUpdates.length, HistoryManager.FLUSH_PROJECT_EVERY_N_OPS) HistoryManager.flushProjectChangesAsync project_id callback()