/* eslint-disable camelcase, */ // 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('@overleaf/logger') 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 () {} } 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 () {} } 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 () {} } 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 () {} } logger.debug( { 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.debug( { 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) } ) }, }