2020-05-06 10:09:15 +00:00
|
|
|
/* eslint-disable
|
|
|
|
camelcase,
|
|
|
|
handle-callback-err,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
2020-05-06 10:08:21 +00:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* DS103: Rewrite code to no longer use __guard__
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
2020-05-06 10:09:33 +00:00
|
|
|
let HistoryManager
|
|
|
|
const async = require('async')
|
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const request = require('request')
|
|
|
|
const Settings = require('settings-sharelatex')
|
|
|
|
const HistoryRedisManager = require('./HistoryRedisManager')
|
|
|
|
const ProjectHistoryRedisManager = require('./ProjectHistoryRedisManager')
|
|
|
|
const RedisManager = require('./RedisManager')
|
|
|
|
const metrics = require('./Metrics')
|
2016-11-28 10:14:42 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
module.exports = HistoryManager = {
|
|
|
|
flushDocChangesAsync(project_id, doc_id) {
|
|
|
|
if (
|
|
|
|
(Settings.apis != null ? Settings.apis.trackchanges : undefined) == null
|
|
|
|
) {
|
|
|
|
logger.warn(
|
|
|
|
{ doc_id },
|
|
|
|
'track changes API is not configured, so not flushing'
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return RedisManager.getHistoryType(doc_id, function (
|
|
|
|
err,
|
|
|
|
projectHistoryType
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
logger.warn({ err, doc_id }, 'error getting history type')
|
|
|
|
}
|
|
|
|
// if there's an error continue and flush to track-changes for safety
|
|
|
|
if (
|
|
|
|
Settings.disableDoubleFlush &&
|
|
|
|
projectHistoryType === 'project-history'
|
|
|
|
) {
|
|
|
|
return logger.debug(
|
|
|
|
{ doc_id, projectHistoryType },
|
|
|
|
'skipping track-changes flush'
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
metrics.inc('history-flush', 1, { status: 'track-changes' })
|
|
|
|
const url = `${Settings.apis.trackchanges.url}/project/${project_id}/doc/${doc_id}/flush`
|
|
|
|
logger.log(
|
|
|
|
{ project_id, doc_id, url, projectHistoryType },
|
|
|
|
'flushing doc in track changes api'
|
|
|
|
)
|
|
|
|
return request.post(url, function (error, res, body) {
|
|
|
|
if (error != null) {
|
|
|
|
return logger.error(
|
|
|
|
{ error, doc_id, project_id },
|
|
|
|
'track changes doc to track changes api'
|
|
|
|
)
|
|
|
|
} else if (res.statusCode < 200 && res.statusCode >= 300) {
|
|
|
|
return logger.error(
|
|
|
|
{ doc_id, project_id },
|
|
|
|
`track changes api returned a failure status code: ${res.statusCode}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
2017-10-05 12:45:29 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
// flush changes in the background
|
|
|
|
flushProjectChangesAsync(project_id) {
|
|
|
|
if (
|
|
|
|
!__guard__(
|
|
|
|
Settings.apis != null ? Settings.apis.project_history : undefined,
|
|
|
|
(x) => x.enabled
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return HistoryManager.flushProjectChanges(
|
|
|
|
project_id,
|
|
|
|
{ background: true },
|
|
|
|
function () {}
|
|
|
|
)
|
|
|
|
},
|
2016-11-28 10:14:42 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
// flush changes and callback (for when we need to know the queue is flushed)
|
|
|
|
flushProjectChanges(project_id, options, callback) {
|
|
|
|
if (callback == null) {
|
|
|
|
callback = function (error) {}
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!__guard__(
|
|
|
|
Settings.apis != null ? Settings.apis.project_history : undefined,
|
|
|
|
(x) => x.enabled
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
if (options.skip_history_flush) {
|
|
|
|
logger.log({ project_id }, 'skipping flush of project history')
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
metrics.inc('history-flush', 1, { status: 'project-history' })
|
|
|
|
const url = `${Settings.apis.project_history.url}/project/${project_id}/flush`
|
|
|
|
const qs = {}
|
|
|
|
if (options.background) {
|
|
|
|
qs.background = true
|
|
|
|
} // pass on the background flush option if present
|
|
|
|
logger.log({ project_id, url, qs }, 'flushing doc in project history api')
|
|
|
|
return request.post({ url, qs }, function (error, res, body) {
|
|
|
|
if (error != null) {
|
|
|
|
logger.error(
|
|
|
|
{ error, project_id },
|
|
|
|
'project history doc to track changes api'
|
|
|
|
)
|
|
|
|
return callback(error)
|
|
|
|
} else if (res.statusCode < 200 && res.statusCode >= 300) {
|
|
|
|
logger.error(
|
|
|
|
{ project_id },
|
|
|
|
`project history api returned a failure status code: ${res.statusCode}`
|
|
|
|
)
|
|
|
|
return callback(error)
|
|
|
|
} else {
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
2016-11-28 10:14:42 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
FLUSH_DOC_EVERY_N_OPS: 100,
|
|
|
|
FLUSH_PROJECT_EVERY_N_OPS: 500,
|
2017-11-13 11:53:39 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
recordAndFlushHistoryOps(
|
|
|
|
project_id,
|
|
|
|
doc_id,
|
|
|
|
ops,
|
|
|
|
doc_ops_length,
|
|
|
|
project_ops_length,
|
|
|
|
callback
|
|
|
|
) {
|
|
|
|
if (ops == null) {
|
|
|
|
ops = []
|
|
|
|
}
|
|
|
|
if (callback == null) {
|
|
|
|
callback = function (error) {}
|
|
|
|
}
|
|
|
|
if (ops.length === 0) {
|
|
|
|
return callback()
|
|
|
|
}
|
2017-10-05 12:45:29 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
// record updates for project history
|
|
|
|
if (
|
|
|
|
__guard__(
|
|
|
|
Settings.apis != null ? Settings.apis.project_history : undefined,
|
|
|
|
(x) => x.enabled
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
HistoryManager.shouldFlushHistoryOps(
|
|
|
|
project_ops_length,
|
|
|
|
ops.length,
|
|
|
|
HistoryManager.FLUSH_PROJECT_EVERY_N_OPS
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// Do this in the background since it uses HTTP and so may be too
|
|
|
|
// slow to wait for when processing a doc update.
|
|
|
|
logger.log(
|
|
|
|
{ project_ops_length, project_id },
|
|
|
|
'flushing project history api'
|
|
|
|
)
|
|
|
|
HistoryManager.flushProjectChangesAsync(project_id)
|
|
|
|
}
|
|
|
|
}
|
2017-10-05 12:45:29 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
// if the doc_ops_length is undefined it means the project is not using track-changes
|
|
|
|
// so we can bail out here
|
|
|
|
if (typeof doc_ops_length === 'undefined') {
|
|
|
|
logger.debug(
|
|
|
|
{ project_id, doc_id },
|
|
|
|
'skipping flush to track-changes, only using project-history'
|
|
|
|
)
|
|
|
|
return callback()
|
|
|
|
}
|
2019-11-15 16:53:16 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
// record updates for track-changes
|
|
|
|
return HistoryRedisManager.recordDocHasHistoryOps(
|
|
|
|
project_id,
|
|
|
|
doc_id,
|
|
|
|
ops,
|
|
|
|
function (error) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
HistoryManager.shouldFlushHistoryOps(
|
|
|
|
doc_ops_length,
|
|
|
|
ops.length,
|
|
|
|
HistoryManager.FLUSH_DOC_EVERY_N_OPS
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// Do this in the background since it uses HTTP and so may be too
|
|
|
|
// slow to wait for when processing a doc update.
|
|
|
|
logger.log(
|
|
|
|
{ doc_ops_length, doc_id, project_id },
|
|
|
|
'flushing track changes api'
|
|
|
|
)
|
|
|
|
HistoryManager.flushDocChangesAsync(project_id, doc_id)
|
|
|
|
}
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
2017-10-05 12:45:29 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
shouldFlushHistoryOps(length, ops_length, threshold) {
|
|
|
|
if (!length) {
|
|
|
|
return false
|
|
|
|
} // don't flush unless we know the length
|
|
|
|
// We want to flush every 100 ops, i.e. 100, 200, 300, etc
|
|
|
|
// Find out which 'block' (i.e. 0-99, 100-199) we were in before and after pushing these
|
|
|
|
// ops. If we've changed, then we've gone over a multiple of 100 and should flush.
|
|
|
|
// (Most of the time, we will only hit 100 and then flushing will put us back to 0)
|
|
|
|
const previousLength = length - ops_length
|
|
|
|
const prevBlock = Math.floor(previousLength / threshold)
|
|
|
|
const newBlock = Math.floor(length / threshold)
|
|
|
|
return newBlock !== prevBlock
|
|
|
|
},
|
2018-02-28 17:31:43 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
MAX_PARALLEL_REQUESTS: 4,
|
2018-08-16 10:13:11 +00:00
|
|
|
|
2020-05-06 10:09:33 +00:00
|
|
|
resyncProjectHistory(project_id, projectHistoryId, docs, files, callback) {
|
|
|
|
return ProjectHistoryRedisManager.queueResyncProjectStructure(
|
|
|
|
project_id,
|
|
|
|
projectHistoryId,
|
|
|
|
docs,
|
|
|
|
files,
|
|
|
|
function (error) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
const DocumentManager = require('./DocumentManager')
|
|
|
|
const resyncDoc = (doc, cb) =>
|
|
|
|
DocumentManager.resyncDocContentsWithLock(project_id, doc.doc, cb)
|
|
|
|
return async.eachLimit(
|
|
|
|
docs,
|
|
|
|
HistoryManager.MAX_PARALLEL_REQUESTS,
|
|
|
|
resyncDoc,
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2020-05-06 10:08:21 +00:00
|
|
|
|
|
|
|
function __guard__(value, transform) {
|
2020-05-06 10:09:33 +00:00
|
|
|
return typeof value !== 'undefined' && value !== null
|
|
|
|
? transform(value)
|
|
|
|
: undefined
|
|
|
|
}
|