DiffManager.js 5.5 KB

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