DiffManager.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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) { callback = function(error, content, version, updates) {}; }
  24. return DocumentUpdaterManager.getDocument(project_id, doc_id, function(error, content, version) {
  25. if (error != null) { return callback(error); }
  26. if ((fromVersion == null)) { // If we haven't been given a version, just return lastest doc and no updates
  27. return callback(null, content, version, []);
  28. }
  29. return UpdatesManager.getDocUpdatesWithUserInfo(project_id, doc_id, {from: fromVersion}, function(error, updates) {
  30. if (error != null) { return callback(error); }
  31. return callback(null, content, version, updates);
  32. });
  33. });
  34. },
  35. getDiff(project_id, doc_id, fromVersion, toVersion, callback) {
  36. if (callback == null) { callback = function(error, diff) {}; }
  37. return DiffManager.getDocumentBeforeVersion(project_id, doc_id, fromVersion, function(error, startingContent, updates) {
  38. let diff;
  39. if (error != null) {
  40. if (error.message === "broken-history") {
  41. return callback(null, "history unavailable");
  42. } else {
  43. return callback(error);
  44. }
  45. }
  46. const updatesToApply = [];
  47. for (const update of Array.from(updates.slice().reverse())) {
  48. if (update.v <= toVersion) {
  49. updatesToApply.push(update);
  50. }
  51. }
  52. try {
  53. diff = DiffGenerator.buildDiff(startingContent, updatesToApply);
  54. } catch (e) {
  55. return callback(e);
  56. }
  57. return callback(null, diff);
  58. });
  59. },
  60. getDocumentBeforeVersion(project_id, doc_id, version, _callback) {
  61. // Whichever order we get the latest document and the latest updates,
  62. // there is potential for updates to be applied between them so that
  63. // they do not return the same 'latest' versions.
  64. // If this happens, we just retry and hopefully get them at the compatible
  65. // versions.
  66. let retry;
  67. if (_callback == null) { _callback = function(error, document, rewoundUpdates) {}; }
  68. let retries = 3;
  69. const callback = function(error, ...args) {
  70. if (error != null) {
  71. if (error.retry && (retries > 0)) {
  72. logger.warn({error, project_id, doc_id, version, retries}, "retrying getDocumentBeforeVersion");
  73. return retry();
  74. } else {
  75. return _callback(error);
  76. }
  77. } else {
  78. return _callback(null, ...Array.from(args));
  79. }
  80. };
  81. return (retry = function() {
  82. retries--;
  83. return DiffManager._tryGetDocumentBeforeVersion(project_id, doc_id, version, callback);
  84. })();
  85. },
  86. _tryGetDocumentBeforeVersion(project_id, doc_id, version, callback) {
  87. if (callback == null) { callback = function(error, document, rewoundUpdates) {}; }
  88. logger.log({project_id, doc_id, version}, "getting document before version");
  89. return DiffManager.getLatestDocAndUpdates(project_id, doc_id, version, function(error, content, version, updates) {
  90. let startingContent;
  91. if (error != null) { return callback(error); }
  92. // bail out if we hit a broken update
  93. for (const u of Array.from(updates)) {
  94. if (u.broken) {
  95. return callback(new Error("broken-history"));
  96. }
  97. }
  98. // discard any updates which are ahead of this document version
  99. while ((updates[0] != null ? updates[0].v : undefined) >= version) {
  100. updates.shift();
  101. }
  102. const lastUpdate = updates[0];
  103. if ((lastUpdate != null) && (lastUpdate.v !== (version - 1))) {
  104. error = new Error(`latest update version, ${lastUpdate.v}, does not match doc version, ${version}`);
  105. error.retry = true;
  106. return callback(error);
  107. }
  108. logger.log({docVersion: version, lastUpdateVersion: (lastUpdate != null ? lastUpdate.v : undefined), updateCount: updates.length}, "rewinding updates");
  109. const tryUpdates = updates.slice().reverse();
  110. try {
  111. startingContent = DiffGenerator.rewindUpdates(content, tryUpdates);
  112. // tryUpdates is reversed, and any unapplied ops are marked as broken
  113. } catch (e) {
  114. return callback(e);
  115. }
  116. return callback(null, startingContent, tryUpdates);
  117. });
  118. }
  119. });