overleaf/services/track-changes/app/js/DiffManager.js
2020-02-17 18:34:25 +01:00

134 lines
4.7 KiB
JavaScript

/* eslint-disable
camelcase,
handle-callback-err,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
let DiffManager;
const UpdatesManager = require("./UpdatesManager");
const DocumentUpdaterManager = require("./DocumentUpdaterManager");
const DiffGenerator = require("./DiffGenerator");
const logger = require("logger-sharelatex");
module.exports = (DiffManager = {
getLatestDocAndUpdates(project_id, doc_id, fromVersion, callback) {
// Get updates last, since then they must be ahead and it
// might be possible to rewind to the same version as the doc.
if (callback == null) { callback = function(error, content, version, updates) {}; }
return DocumentUpdaterManager.getDocument(project_id, doc_id, function(error, content, version) {
if (error != null) { return callback(error); }
if ((fromVersion == null)) { // If we haven't been given a version, just return lastest doc and no updates
return callback(null, content, version, []);
}
return UpdatesManager.getDocUpdatesWithUserInfo(project_id, doc_id, {from: fromVersion}, function(error, updates) {
if (error != null) { return callback(error); }
return callback(null, content, version, updates);
});
});
},
getDiff(project_id, doc_id, fromVersion, toVersion, callback) {
if (callback == null) { callback = function(error, diff) {}; }
return DiffManager.getDocumentBeforeVersion(project_id, doc_id, fromVersion, function(error, startingContent, updates) {
let diff;
if (error != null) {
if (error.message === "broken-history") {
return callback(null, "history unavailable");
} else {
return callback(error);
}
}
const updatesToApply = [];
for (const update of Array.from(updates.slice().reverse())) {
if (update.v <= toVersion) {
updatesToApply.push(update);
}
}
try {
diff = DiffGenerator.buildDiff(startingContent, updatesToApply);
} catch (e) {
return callback(e);
}
return callback(null, diff);
});
},
getDocumentBeforeVersion(project_id, doc_id, version, _callback) {
// Whichever order we get the latest document and the latest updates,
// there is potential for updates to be applied between them so that
// they do not return the same 'latest' versions.
// If this happens, we just retry and hopefully get them at the compatible
// versions.
let retry;
if (_callback == null) { _callback = function(error, document, rewoundUpdates) {}; }
let retries = 3;
const callback = function(error, ...args) {
if (error != null) {
if (error.retry && (retries > 0)) {
logger.warn({error, project_id, doc_id, version, retries}, "retrying getDocumentBeforeVersion");
return retry();
} else {
return _callback(error);
}
} else {
return _callback(null, ...Array.from(args));
}
};
return (retry = function() {
retries--;
return DiffManager._tryGetDocumentBeforeVersion(project_id, doc_id, version, callback);
})();
},
_tryGetDocumentBeforeVersion(project_id, doc_id, version, callback) {
if (callback == null) { callback = function(error, document, rewoundUpdates) {}; }
logger.log({project_id, doc_id, version}, "getting document before version");
return DiffManager.getLatestDocAndUpdates(project_id, doc_id, version, function(error, content, version, updates) {
let startingContent;
if (error != null) { return callback(error); }
// bail out if we hit a broken update
for (const u of Array.from(updates)) {
if (u.broken) {
return callback(new Error("broken-history"));
}
}
// discard any updates which are ahead of this document version
while ((updates[0] != null ? updates[0].v : undefined) >= version) {
updates.shift();
}
const lastUpdate = updates[0];
if ((lastUpdate != null) && (lastUpdate.v !== (version - 1))) {
error = new Error(`latest update version, ${lastUpdate.v}, does not match doc version, ${version}`);
error.retry = true;
return callback(error);
}
logger.log({docVersion: version, lastUpdateVersion: (lastUpdate != null ? lastUpdate.v : undefined), updateCount: updates.length}, "rewinding updates");
const tryUpdates = updates.slice().reverse();
try {
startingContent = DiffGenerator.rewindUpdates(content, tryUpdates);
// tryUpdates is reversed, and any unapplied ops are marked as broken
} catch (e) {
return callback(e);
}
return callback(null, startingContent, tryUpdates);
});
}
});