ProjectManager.coffee 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. RedisManager = require "./RedisManager"
  2. ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
  3. DocumentManager = require "./DocumentManager"
  4. HistoryManager = require "./HistoryManager"
  5. async = require "async"
  6. logger = require "logger-sharelatex"
  7. Metrics = require "./Metrics"
  8. Errors = require "./Errors"
  9. module.exports = ProjectManager =
  10. flushProjectWithLocks: (project_id, _callback = (error) ->) ->
  11. timer = new Metrics.Timer("projectManager.flushProjectWithLocks")
  12. callback = (args...) ->
  13. timer.done()
  14. _callback(args...)
  15. RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
  16. return callback(error) if error?
  17. jobs = []
  18. errors = []
  19. for doc_id in (doc_ids or [])
  20. do (doc_id) ->
  21. jobs.push (callback) ->
  22. DocumentManager.flushDocIfLoadedWithLock project_id, doc_id, (error) ->
  23. if error? and error instanceof Errors.NotFoundError
  24. logger.warn err: error, project_id: project_id, doc_id: doc_id, "found deleted doc when flushing"
  25. callback()
  26. else if error?
  27. logger.error err: error, project_id: project_id, doc_id: doc_id, "error flushing doc"
  28. errors.push(error)
  29. callback()
  30. else
  31. callback()
  32. logger.log project_id: project_id, doc_ids: doc_ids, "flushing docs"
  33. async.series jobs, () ->
  34. if errors.length > 0
  35. callback new Error("Errors flushing docs. See log for details")
  36. else
  37. callback(null)
  38. flushAndDeleteProjectWithLocks: (project_id, options, _callback = (error) ->) ->
  39. timer = new Metrics.Timer("projectManager.flushAndDeleteProjectWithLocks")
  40. callback = (args...) ->
  41. timer.done()
  42. _callback(args...)
  43. RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
  44. return callback(error) if error?
  45. jobs = []
  46. errors = []
  47. for doc_id in (doc_ids or [])
  48. do (doc_id) ->
  49. jobs.push (callback) ->
  50. DocumentManager.flushAndDeleteDocWithLock project_id, doc_id, {}, (error) ->
  51. if error?
  52. logger.error err: error, project_id: project_id, doc_id: doc_id, "error deleting doc"
  53. errors.push(error)
  54. callback()
  55. logger.log project_id: project_id, doc_ids: doc_ids, "deleting docs"
  56. async.series jobs, () ->
  57. # When deleting the project here we want to ensure that project
  58. # history is completely flushed because the project may be
  59. # deleted in web after this call completes, and so further
  60. # attempts to flush would fail after that.
  61. HistoryManager.flushProjectChanges project_id, options, (error) ->
  62. if errors.length > 0
  63. callback new Error("Errors deleting docs. See log for details")
  64. else if error?
  65. callback(error)
  66. else
  67. callback(null)
  68. queueFlushAndDeleteProject: (project_id, callback = (error) ->) ->
  69. RedisManager.queueFlushAndDeleteProject project_id, (error) ->
  70. if error?
  71. logger.error {project_id: project_id, error:error}, "error adding project to flush and delete queue"
  72. return callback(error)
  73. Metrics.inc "queued-delete"
  74. callback()
  75. getProjectDocsTimestamps: (project_id, callback = (error) ->) ->
  76. RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
  77. return callback(error) if error?
  78. return callback(null, []) if !doc_ids?.length
  79. RedisManager.getDocTimestamps doc_ids, (error, timestamps) ->
  80. return callback(error) if error?
  81. callback(null, timestamps)
  82. getProjectDocsAndFlushIfOld: (project_id, projectStateHash, excludeVersions = {}, _callback = (error, docs) ->) ->
  83. timer = new Metrics.Timer("projectManager.getProjectDocsAndFlushIfOld")
  84. callback = (args...) ->
  85. timer.done()
  86. _callback(args...)
  87. RedisManager.checkOrSetProjectState project_id, projectStateHash, (error, projectStateChanged) ->
  88. if error?
  89. logger.error err: error, project_id: project_id, "error getting/setting project state in getProjectDocsAndFlushIfOld"
  90. return callback(error)
  91. # we can't return docs if project structure has changed
  92. if projectStateChanged
  93. return callback Errors.ProjectStateChangedError("project state changed")
  94. # project structure hasn't changed, return doc content from redis
  95. RedisManager.getDocIdsInProject project_id, (error, doc_ids) ->
  96. if error?
  97. logger.error err: error, project_id: project_id, "error getting doc ids in getProjectDocs"
  98. return callback(error)
  99. jobs = []
  100. for doc_id in doc_ids or []
  101. do (doc_id) ->
  102. jobs.push (cb) ->
  103. # get the doc lines from redis
  104. DocumentManager.getDocAndFlushIfOldWithLock project_id, doc_id, (err, lines, version) ->
  105. if err?
  106. logger.error err:err, project_id: project_id, doc_id: doc_id, "error getting project doc lines in getProjectDocsAndFlushIfOld"
  107. return cb(err)
  108. doc = {_id:doc_id, lines:lines, v:version} # create a doc object to return
  109. cb(null, doc)
  110. async.series jobs, (error, docs) ->
  111. return callback(error) if error?
  112. callback(null, docs)
  113. clearProjectState: (project_id, callback = (error) ->) ->
  114. RedisManager.clearProjectState project_id, callback
  115. updateProjectWithLocks: (project_id, projectHistoryId, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) ->
  116. timer = new Metrics.Timer("projectManager.updateProject")
  117. callback = (args...) ->
  118. timer.done()
  119. _callback(args...)
  120. project_version = version
  121. project_subversion = 0 # project versions can have multiple operations
  122. project_ops_length = 0
  123. handleDocUpdate = (projectUpdate, cb) ->
  124. doc_id = projectUpdate.id
  125. projectUpdate.version = "#{project_version}.#{project_subversion++}"
  126. if projectUpdate.docLines?
  127. ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'doc', doc_id, user_id, projectUpdate, (error, count) ->
  128. project_ops_length = count
  129. cb(error)
  130. else
  131. DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, projectHistoryId, (error, count) ->
  132. project_ops_length = count
  133. cb(error)
  134. handleFileUpdate = (projectUpdate, cb) ->
  135. file_id = projectUpdate.id
  136. projectUpdate.version = "#{project_version}.#{project_subversion++}"
  137. if projectUpdate.url?
  138. ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
  139. project_ops_length = count
  140. cb(error)
  141. else
  142. ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
  143. project_ops_length = count
  144. cb(error)
  145. async.eachSeries docUpdates, handleDocUpdate, (error) ->
  146. return callback(error) if error?
  147. async.eachSeries fileUpdates, handleFileUpdate, (error) ->
  148. return callback(error) if error?
  149. if HistoryManager.shouldFlushHistoryOps(project_ops_length, docUpdates.length + fileUpdates.length, HistoryManager.FLUSH_PROJECT_EVERY_N_OPS)
  150. HistoryManager.flushProjectChangesAsync project_id
  151. callback()