DiffManager.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /* eslint-disable
  2. camelcase,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. let DiffManager
  14. const UpdatesManager = require('./UpdatesManager')
  15. const DocumentUpdaterManager = require('./DocumentUpdaterManager')
  16. const DiffGenerator = require('./DiffGenerator')
  17. const logger = require('@overleaf/logger')
  18. module.exports = DiffManager = {
  19. getLatestDocAndUpdates(project_id, doc_id, fromVersion, callback) {
  20. // Get updates last, since then they must be ahead and it
  21. // might be possible to rewind to the same version as the doc.
  22. if (callback == null) {
  23. callback = function () {}
  24. }
  25. return DocumentUpdaterManager.getDocument(
  26. project_id,
  27. doc_id,
  28. function (error, content, version) {
  29. if (error != null) {
  30. return callback(error)
  31. }
  32. if (fromVersion == null) {
  33. // If we haven't been given a version, just return lastest doc and no updates
  34. return callback(null, content, version, [])
  35. }
  36. return UpdatesManager.getDocUpdatesWithUserInfo(
  37. project_id,
  38. doc_id,
  39. { from: fromVersion },
  40. function (error, updates) {
  41. if (error != null) {
  42. return callback(error)
  43. }
  44. return callback(null, content, version, updates)
  45. }
  46. )
  47. }
  48. )
  49. },
  50. getDiff(project_id, doc_id, fromVersion, toVersion, callback) {
  51. if (callback == null) {
  52. callback = function () {}
  53. }
  54. return DiffManager.getDocumentBeforeVersion(
  55. project_id,
  56. doc_id,
  57. fromVersion,
  58. function (error, startingContent, updates) {
  59. let diff
  60. if (error != null) {
  61. if (error.message === 'broken-history') {
  62. return callback(null, 'history unavailable')
  63. } else {
  64. return callback(error)
  65. }
  66. }
  67. const updatesToApply = []
  68. for (const update of Array.from(updates.slice().reverse())) {
  69. if (update.v <= toVersion) {
  70. updatesToApply.push(update)
  71. }
  72. }
  73. try {
  74. diff = DiffGenerator.buildDiff(startingContent, updatesToApply)
  75. } catch (e) {
  76. return callback(e)
  77. }
  78. return callback(null, diff)
  79. }
  80. )
  81. },
  82. getDocumentBeforeVersion(project_id, doc_id, version, _callback) {
  83. // Whichever order we get the latest document and the latest updates,
  84. // there is potential for updates to be applied between them so that
  85. // they do not return the same 'latest' versions.
  86. // If this happens, we just retry and hopefully get them at the compatible
  87. // versions.
  88. let retry
  89. if (_callback == null) {
  90. _callback = function () {}
  91. }
  92. let retries = 3
  93. const callback = function (error, ...args) {
  94. if (error != null) {
  95. if (error.retry && retries > 0) {
  96. logger.warn(
  97. { error, project_id, doc_id, version, retries },
  98. 'retrying getDocumentBeforeVersion'
  99. )
  100. return retry()
  101. } else {
  102. return _callback(error)
  103. }
  104. } else {
  105. return _callback(null, ...Array.from(args))
  106. }
  107. }
  108. return (retry = function () {
  109. retries--
  110. return DiffManager._tryGetDocumentBeforeVersion(
  111. project_id,
  112. doc_id,
  113. version,
  114. callback
  115. )
  116. })()
  117. },
  118. _tryGetDocumentBeforeVersion(project_id, doc_id, version, callback) {
  119. if (callback == null) {
  120. callback = function () {}
  121. }
  122. logger.debug(
  123. { project_id, doc_id, version },
  124. 'getting document before version'
  125. )
  126. return DiffManager.getLatestDocAndUpdates(
  127. project_id,
  128. doc_id,
  129. version,
  130. function (error, content, version, updates) {
  131. let startingContent
  132. if (error != null) {
  133. return callback(error)
  134. }
  135. // bail out if we hit a broken update
  136. for (const u of Array.from(updates)) {
  137. if (u.broken) {
  138. return callback(new Error('broken-history'))
  139. }
  140. }
  141. // discard any updates which are ahead of this document version
  142. while ((updates[0] != null ? updates[0].v : undefined) >= version) {
  143. updates.shift()
  144. }
  145. const lastUpdate = updates[0]
  146. if (lastUpdate != null && lastUpdate.v !== version - 1) {
  147. error = new Error(
  148. `latest update version, ${lastUpdate.v}, does not match doc version, ${version}`
  149. )
  150. error.retry = true
  151. return callback(error)
  152. }
  153. logger.debug(
  154. {
  155. docVersion: version,
  156. lastUpdateVersion: lastUpdate != null ? lastUpdate.v : undefined,
  157. updateCount: updates.length,
  158. },
  159. 'rewinding updates'
  160. )
  161. const tryUpdates = updates.slice().reverse()
  162. try {
  163. startingContent = DiffGenerator.rewindUpdates(content, tryUpdates)
  164. // tryUpdates is reversed, and any unapplied ops are marked as broken
  165. } catch (e) {
  166. return callback(e)
  167. }
  168. return callback(null, startingContent, tryUpdates)
  169. }
  170. )
  171. },
  172. }