DocumentManager.coffee 9.4 KB

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