mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-25 20:00:11 +02:00

* [history-ot] rename remaining history-v1-ot references to history-ot * [web] rename History-v1 OT -> History OT in admin panel * [web] rename OT Migration -> History OT Migration in admin panel GitOrigin-RevId: 103ce816d5320d6379d51009cdc08b8a71aa48e6
158 lines
4.1 KiB
JavaScript
158 lines
4.1 KiB
JavaScript
// @ts-check
|
|
|
|
const Profiler = require('./Profiler')
|
|
const DocumentManager = require('./DocumentManager')
|
|
const Errors = require('./Errors')
|
|
const RedisManager = require('./RedisManager')
|
|
const {
|
|
EditOperationBuilder,
|
|
StringFileData,
|
|
EditOperationTransformer,
|
|
} = require('overleaf-editor-core')
|
|
const Metrics = require('./Metrics')
|
|
const ProjectHistoryRedisManager = require('./ProjectHistoryRedisManager')
|
|
const HistoryManager = require('./HistoryManager')
|
|
const RealTimeRedisManager = require('./RealTimeRedisManager')
|
|
|
|
/**
|
|
* @typedef {import("./types").Update} Update
|
|
* @typedef {import("./types").HistoryOTEditOperationUpdate} HistoryOTEditOperationUpdate
|
|
*/
|
|
|
|
/**
|
|
* @param {Update} update
|
|
* @return {update is HistoryOTEditOperationUpdate}
|
|
*/
|
|
function isHistoryOTEditOperationUpdate(update) {
|
|
return (
|
|
update &&
|
|
'doc' in update &&
|
|
'op' in update &&
|
|
'v' in update &&
|
|
Array.isArray(update.op) &&
|
|
EditOperationBuilder.isValid(update.op[0])
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Try to apply an update to the given document
|
|
*
|
|
* @param {string} projectId
|
|
* @param {string} docId
|
|
* @param {HistoryOTEditOperationUpdate} update
|
|
* @param {Profiler} profiler
|
|
*/
|
|
async function tryApplyUpdate(projectId, docId, update, profiler) {
|
|
let { lines, version, pathname, type } =
|
|
await DocumentManager.promises.getDoc(projectId, docId)
|
|
profiler.log('getDoc')
|
|
|
|
if (lines == null || version == null) {
|
|
throw new Errors.NotFoundError(`document not found: ${docId}`)
|
|
}
|
|
if (type !== 'history-ot') {
|
|
throw new Errors.OTTypeMismatchError(type, 'history-ot')
|
|
}
|
|
|
|
let op = EditOperationBuilder.fromJSON(update.op[0])
|
|
if (version !== update.v) {
|
|
const transformUpdates = await RedisManager.promises.getPreviousDocOps(
|
|
docId,
|
|
update.v,
|
|
version
|
|
)
|
|
for (const transformUpdate of transformUpdates) {
|
|
if (!isHistoryOTEditOperationUpdate(transformUpdate)) {
|
|
throw new Errors.OTTypeMismatchError('sharejs-text-ot', 'history-ot')
|
|
}
|
|
|
|
if (
|
|
transformUpdate.meta.source &&
|
|
update.dupIfSource?.includes(transformUpdate.meta.source)
|
|
) {
|
|
update.dup = true
|
|
break
|
|
}
|
|
const other = EditOperationBuilder.fromJSON(transformUpdate.op[0])
|
|
op = EditOperationTransformer.transform(op, other)[0]
|
|
}
|
|
update.op = [op.toJSON()]
|
|
}
|
|
|
|
if (!update.dup) {
|
|
const file = StringFileData.fromRaw(lines)
|
|
file.edit(op)
|
|
version += 1
|
|
update.meta.ts = Date.now()
|
|
await RedisManager.promises.updateDocument(
|
|
projectId,
|
|
docId,
|
|
file.toRaw(),
|
|
version,
|
|
[update],
|
|
{},
|
|
update.meta
|
|
)
|
|
|
|
Metrics.inc('history-queue', 1, { status: 'project-history' })
|
|
try {
|
|
const projectOpsLength =
|
|
await ProjectHistoryRedisManager.promises.queueOps(projectId, [
|
|
JSON.stringify({
|
|
...update,
|
|
meta: {
|
|
...update.meta,
|
|
pathname,
|
|
},
|
|
}),
|
|
])
|
|
HistoryManager.recordAndFlushHistoryOps(
|
|
projectId,
|
|
[update],
|
|
projectOpsLength
|
|
)
|
|
profiler.log('recordAndFlushHistoryOps')
|
|
} catch (err) {
|
|
// The full project history can re-sync a project in case
|
|
// updates went missing.
|
|
// Just record the error here and acknowledge the write-op.
|
|
Metrics.inc('history-queue-error')
|
|
}
|
|
}
|
|
RealTimeRedisManager.sendData({
|
|
project_id: projectId,
|
|
doc_id: docId,
|
|
op: update,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Apply an update to the given document
|
|
*
|
|
* @param {string} projectId
|
|
* @param {string} docId
|
|
* @param {HistoryOTEditOperationUpdate} update
|
|
*/
|
|
async function applyUpdate(projectId, docId, update) {
|
|
const profiler = new Profiler('applyUpdate', {
|
|
project_id: projectId,
|
|
doc_id: docId,
|
|
type: 'history-ot',
|
|
})
|
|
|
|
try {
|
|
await tryApplyUpdate(projectId, docId, update, profiler)
|
|
} catch (error) {
|
|
RealTimeRedisManager.sendData({
|
|
project_id: projectId,
|
|
doc_id: docId,
|
|
error: error instanceof Error ? error.message : error,
|
|
})
|
|
profiler.log('sendData')
|
|
throw error
|
|
} finally {
|
|
profiler.end()
|
|
}
|
|
}
|
|
|
|
module.exports = { isHistoryOTEditOperationUpdate, applyUpdate }
|