DocumentManager.coffee 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. RedisManager = require "./RedisManager"
  2. ProjectHistoryRedisManager = require "./ProjectHistoryRedisManager"
  3. PersistenceManager = require "./PersistenceManager"
  4. DiffCodec = require "./DiffCodec"
  5. logger = require "logger-sharelatex"
  6. Metrics = require "./Metrics"
  7. HistoryManager = require "./HistoryManager"
  8. RealTimeRedisManager = require "./RealTimeRedisManager"
  9. Errors = require "./Errors"
  10. RangesManager = require "./RangesManager"
  11. async = require "async"
  12. MAX_UNFLUSHED_AGE = 300 * 1000 # 5 mins, document should be flushed to mongo this time after a change
  13. module.exports = DocumentManager =
  14. getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->) ->
  15. timer = new Metrics.Timer("docManager.getDoc")
  16. callback = (args...) ->
  17. timer.done()
  18. _callback(args...)
  19. RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime) ->
  20. return callback(error) if error?
  21. if !lines? or !version?
  22. logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API"
  23. PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
  24. return callback(error) if error?
  25. logger.log {project_id, doc_id, lines, version, pathname, projectHistoryId}, "got doc from persistence API"
  26. RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, projectHistoryId, (error) ->
  27. return callback(error) if error?
  28. callback null, lines, version, ranges, pathname, projectHistoryId, null, false
  29. else
  30. callback null, lines, version, ranges, pathname, projectHistoryId, unflushedTime, true
  31. getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, ops, ranges, pathname, projectHistoryId) ->) ->
  32. timer = new Metrics.Timer("docManager.getDocAndRecentOps")
  33. callback = (args...) ->
  34. timer.done()
  35. _callback(args...)
  36. DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
  37. return callback(error) if error?
  38. if fromVersion == -1
  39. callback null, lines, version, [], ranges, pathname, projectHistoryId
  40. else
  41. RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) ->
  42. return callback(error) if error?
  43. callback null, lines, version, ops, ranges, pathname, projectHistoryId
  44. setDoc: (project_id, doc_id, newLines, source, user_id, undoing, _callback = (error) ->) ->
  45. timer = new Metrics.Timer("docManager.setDoc")
  46. callback = (args...) ->
  47. timer.done()
  48. _callback(args...)
  49. if !newLines?
  50. return callback(new Error("No lines were provided to setDoc"))
  51. UpdateManager = require "./UpdateManager"
  52. DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->
  53. return callback(error) if error?
  54. if oldLines? and oldLines.length > 0 and oldLines[0].text?
  55. logger.log doc_id: doc_id, project_id: project_id, oldLines: oldLines, newLines: newLines, "document is JSON so not updating"
  56. return callback(null)
  57. logger.log doc_id: doc_id, project_id: project_id, oldLines: oldLines, newLines: newLines, "setting a document via http"
  58. DiffCodec.diffAsShareJsOp oldLines, newLines, (error, op) ->
  59. return callback(error) if error?
  60. if undoing
  61. for o in op or []
  62. o.u = true # Turn on undo flag for each op for track changes
  63. update =
  64. doc: doc_id
  65. op: op
  66. v: version
  67. meta:
  68. type: "external"
  69. source: source
  70. user_id: user_id
  71. UpdateManager.applyUpdate project_id, doc_id, update, (error) ->
  72. return callback(error) if error?
  73. # If the document was loaded already, then someone has it open
  74. # in a project, and the usual flushing mechanism will happen.
  75. # Otherwise we should remove it immediately since nothing else
  76. # is using it.
  77. if alreadyLoaded
  78. DocumentManager.flushDocIfLoaded project_id, doc_id, (error) ->
  79. return callback(error) if error?
  80. callback null
  81. else
  82. DocumentManager.flushAndDeleteDoc project_id, doc_id, (error) ->
  83. # There is no harm in flushing project history if the previous
  84. # call failed and sometimes it is required
  85. HistoryManager.flushProjectChangesAsync project_id
  86. return callback(error) if error?
  87. callback null
  88. flushDocIfLoaded: (project_id, doc_id, _callback = (error) ->) ->
  89. timer = new Metrics.Timer("docManager.flushDocIfLoaded")
  90. callback = (args...) ->
  91. timer.done()
  92. _callback(args...)
  93. RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy) ->
  94. return callback(error) if error?
  95. if !lines? or !version?
  96. logger.log project_id: project_id, doc_id: doc_id, "doc is not loaded so not flushing"
  97. callback null # TODO: return a flag to bail out, as we go on to remove doc from memory?
  98. else
  99. logger.log project_id: project_id, doc_id: doc_id, version: version, "flushing doc"
  100. PersistenceManager.setDoc project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy, (error) ->
  101. return callback(error) if error?
  102. RedisManager.clearUnflushedTime doc_id, callback
  103. flushAndDeleteDoc: (project_id, doc_id, _callback = (error) ->) ->
  104. timer = new Metrics.Timer("docManager.flushAndDeleteDoc")
  105. callback = (args...) ->
  106. timer.done()
  107. _callback(args...)
  108. DocumentManager.flushDocIfLoaded project_id, doc_id, (error) ->
  109. return callback(error) if error?
  110. # Flush in the background since it requires a http request
  111. HistoryManager.flushDocChangesAsync project_id, doc_id
  112. RedisManager.removeDocFromMemory project_id, doc_id, (error) ->
  113. return callback(error) if error?
  114. callback null
  115. acceptChanges: (project_id, doc_id, change_ids = [], _callback = (error) ->) ->
  116. timer = new Metrics.Timer("docManager.acceptChanges")
  117. callback = (args...) ->
  118. timer.done()
  119. _callback(args...)
  120. DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
  121. return callback(error) if error?
  122. if !lines? or !version?
  123. return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
  124. RangesManager.acceptChanges change_ids, ranges, (error, new_ranges) ->
  125. return callback(error) if error?
  126. RedisManager.updateDocument project_id, doc_id, lines, version, [], new_ranges, {}, (error) ->
  127. return callback(error) if error?
  128. callback()
  129. deleteComment: (project_id, doc_id, comment_id, _callback = (error) ->) ->
  130. timer = new Metrics.Timer("docManager.deleteComment")
  131. callback = (args...) ->
  132. timer.done()
  133. _callback(args...)
  134. DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
  135. return callback(error) if error?
  136. if !lines? or !version?
  137. return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
  138. RangesManager.deleteComment comment_id, ranges, (error, new_ranges) ->
  139. return callback(error) if error?
  140. RedisManager.updateDocument project_id, doc_id, lines, version, [], new_ranges, {}, (error) ->
  141. return callback(error) if error?
  142. callback()
  143. renameDoc: (project_id, doc_id, user_id, update, projectHistoryId, _callback = (error) ->) ->
  144. timer = new Metrics.Timer("docManager.updateProject")
  145. callback = (args...) ->
  146. timer.done()
  147. _callback(args...)
  148. RedisManager.renameDoc project_id, doc_id, user_id, update, projectHistoryId, callback
  149. getDocAndFlushIfOld: (project_id, doc_id, callback = (error, doc) ->) ->
  150. DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->
  151. return callback(error) if error?
  152. # if doc was already loaded see if it needs to be flushed
  153. if alreadyLoaded and unflushedTime? and (Date.now() - unflushedTime) > MAX_UNFLUSHED_AGE
  154. DocumentManager.flushDocIfLoaded project_id, doc_id, (error) ->
  155. return callback(error) if error?
  156. callback(null, lines, version)
  157. else
  158. callback(null, lines, version)
  159. resyncDocContents: (project_id, doc_id, callback) ->
  160. logger.log {project_id: project_id, doc_id: doc_id}, "start resyncing doc contents"
  161. RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
  162. return callback(error) if error?
  163. if !lines? or !version?
  164. logger.log {project_id: project_id, doc_id: doc_id}, "resyncing doc contents - not found in redis - retrieving from web"
  165. PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
  166. if error?
  167. logger.error {project_id: project_id, doc_id: doc_id, getDocError: error}, "resyncing doc contents - error retrieving from web"
  168. return callback(error)
  169. ProjectHistoryRedisManager.queueResyncDocContent project_id, projectHistoryId, doc_id, lines, version, pathname, callback
  170. else
  171. logger.log {project_id: project_id, doc_id: doc_id}, "resyncing doc contents - doc in redis - will queue in redis"
  172. ProjectHistoryRedisManager.queueResyncDocContent project_id, projectHistoryId, doc_id, lines, version, pathname, callback
  173. getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
  174. UpdateManager = require "./UpdateManager"
  175. UpdateManager.lockUpdatesAndDo DocumentManager.getDoc, project_id, doc_id, callback
  176. getDocAndRecentOpsWithLock: (project_id, doc_id, fromVersion, callback = (error, lines, version, ops, ranges, pathname, projectHistoryId) ->) ->
  177. UpdateManager = require "./UpdateManager"
  178. UpdateManager.lockUpdatesAndDo DocumentManager.getDocAndRecentOps, project_id, doc_id, fromVersion, callback
  179. getDocAndFlushIfOldWithLock: (project_id, doc_id, callback = (error, doc) ->) ->
  180. UpdateManager = require "./UpdateManager"
  181. UpdateManager.lockUpdatesAndDo DocumentManager.getDocAndFlushIfOld, project_id, doc_id, callback
  182. setDocWithLock: (project_id, doc_id, lines, source, user_id, undoing, callback = (error) ->) ->
  183. UpdateManager = require "./UpdateManager"
  184. UpdateManager.lockUpdatesAndDo DocumentManager.setDoc, project_id, doc_id, lines, source, user_id, undoing, callback
  185. flushDocIfLoadedWithLock: (project_id, doc_id, callback = (error) ->) ->
  186. UpdateManager = require "./UpdateManager"
  187. UpdateManager.lockUpdatesAndDo DocumentManager.flushDocIfLoaded, project_id, doc_id, callback
  188. flushAndDeleteDocWithLock: (project_id, doc_id, callback = (error) ->) ->
  189. UpdateManager = require "./UpdateManager"
  190. UpdateManager.lockUpdatesAndDo DocumentManager.flushAndDeleteDoc, project_id, doc_id, callback
  191. acceptChangesWithLock: (project_id, doc_id, change_ids, callback = (error) ->) ->
  192. UpdateManager = require "./UpdateManager"
  193. UpdateManager.lockUpdatesAndDo DocumentManager.acceptChanges, project_id, doc_id, change_ids, callback
  194. deleteCommentWithLock: (project_id, doc_id, thread_id, callback = (error) ->) ->
  195. UpdateManager = require "./UpdateManager"
  196. UpdateManager.lockUpdatesAndDo DocumentManager.deleteComment, project_id, doc_id, thread_id, callback
  197. renameDocWithLock: (project_id, doc_id, user_id, update, projectHistoryId, callback = (error) ->) ->
  198. UpdateManager = require "./UpdateManager"
  199. UpdateManager.lockUpdatesAndDo DocumentManager.renameDoc, project_id, doc_id, user_id, update, projectHistoryId, callback
  200. resyncDocContentsWithLock: (project_id, doc_id, callback = (error) ->) ->
  201. UpdateManager = require "./UpdateManager"
  202. UpdateManager.lockUpdatesAndDo DocumentManager.resyncDocContents, project_id, doc_id, callback