2020-05-06 06:12:47 -04:00
|
|
|
let DocUpdaterClient
|
2021-07-12 12:47:15 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2021-02-02 10:10:04 -05:00
|
|
|
const _ = require('lodash')
|
2020-11-10 06:32:04 -05:00
|
|
|
const rclient = require('@overleaf/redis-wrapper').createClient(
|
2020-05-06 06:12:47 -04:00
|
|
|
Settings.redis.documentupdater
|
|
|
|
)
|
|
|
|
const keys = Settings.redis.documentupdater.key_schema
|
|
|
|
const request = require('request').defaults({ jar: false })
|
|
|
|
const async = require('async')
|
|
|
|
|
2020-11-10 06:32:04 -05:00
|
|
|
const rclientSub = require('@overleaf/redis-wrapper').createClient(
|
2020-05-06 06:12:47 -04:00
|
|
|
Settings.redis.pubsub
|
|
|
|
)
|
2020-05-20 16:12:27 -04:00
|
|
|
rclientSub.subscribe('applied-ops')
|
|
|
|
rclientSub.setMaxListeners(0)
|
2020-05-06 06:12:47 -04:00
|
|
|
|
|
|
|
module.exports = DocUpdaterClient = {
|
|
|
|
randomId() {
|
2020-05-20 16:06:42 -04:00
|
|
|
let str = ''
|
|
|
|
for (let i = 0; i < 24; i++) {
|
|
|
|
str += Math.floor(Math.random() * 16).toString(16)
|
|
|
|
}
|
|
|
|
return str
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
subscribeToAppliedOps(callback) {
|
2020-05-20 16:12:27 -04:00
|
|
|
rclientSub.on('message', callback)
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
|
2021-02-02 10:10:04 -05:00
|
|
|
_getPendingUpdateListKey() {
|
2021-02-09 05:50:37 -05:00
|
|
|
const shard = _.random(0, Settings.dispatcherCount - 1)
|
2021-02-02 10:10:04 -05:00
|
|
|
if (shard === 0) {
|
|
|
|
return 'pending-updates-list'
|
|
|
|
} else {
|
|
|
|
return `pending-updates-list-${shard}`
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
sendUpdate(projectId, docId, update, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
rclient.rpush(
|
2020-05-20 16:12:27 -04:00
|
|
|
keys.pendingUpdates({ doc_id: docId }),
|
2020-05-06 06:12:47 -04:00
|
|
|
JSON.stringify(update),
|
2021-07-13 07:04:42 -04:00
|
|
|
error => {
|
2020-05-20 15:53:06 -04:00
|
|
|
if (error) {
|
2020-05-06 06:12:47 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
2020-05-20 16:12:27 -04:00
|
|
|
const docKey = `${projectId}:${docId}`
|
2021-07-13 07:04:42 -04:00
|
|
|
rclient.sadd('DocsWithPendingUpdates', docKey, error => {
|
2020-05-20 15:53:06 -04:00
|
|
|
if (error) {
|
2020-05-06 06:12:47 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
2021-02-02 10:10:04 -05:00
|
|
|
|
|
|
|
rclient.rpush(
|
|
|
|
DocUpdaterClient._getPendingUpdateListKey(),
|
|
|
|
docKey,
|
|
|
|
callback
|
|
|
|
)
|
2020-05-06 06:12:17 -04:00
|
|
|
})
|
2020-05-06 06:12:47 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
sendUpdates(projectId, docId, updates, callback) {
|
2021-07-13 07:04:42 -04:00
|
|
|
DocUpdaterClient.preloadDoc(projectId, docId, error => {
|
2020-05-20 15:53:06 -04:00
|
|
|
if (error) {
|
2020-05-06 06:12:47 -04:00
|
|
|
return callback(error)
|
|
|
|
}
|
2021-07-13 07:04:42 -04:00
|
|
|
const jobs = updates.map(update => callback => {
|
2020-05-20 16:12:27 -04:00
|
|
|
DocUpdaterClient.sendUpdate(projectId, docId, update, callback)
|
2020-05-20 16:06:42 -04:00
|
|
|
})
|
2021-07-13 07:04:42 -04:00
|
|
|
async.series(jobs, err => {
|
2020-05-20 16:08:03 -04:00
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2020-05-20 16:12:27 -04:00
|
|
|
DocUpdaterClient.waitForPendingUpdates(projectId, docId, callback)
|
2020-05-20 16:08:03 -04:00
|
|
|
})
|
2020-05-06 06:12:47 -04:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
waitForPendingUpdates(projectId, docId, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
async.retry(
|
2020-05-06 06:12:47 -04:00
|
|
|
{ times: 30, interval: 100 },
|
2021-07-13 07:04:42 -04:00
|
|
|
cb =>
|
2020-05-20 16:12:27 -04:00
|
|
|
rclient.llen(keys.pendingUpdates({ doc_id: docId }), (err, length) => {
|
2020-05-20 16:08:03 -04:00
|
|
|
if (err) {
|
|
|
|
return cb(err)
|
|
|
|
}
|
2020-05-06 06:12:47 -04:00
|
|
|
if (length > 0) {
|
2020-05-20 15:54:36 -04:00
|
|
|
cb(new Error('updates still pending'))
|
2020-05-06 06:12:47 -04:00
|
|
|
} else {
|
2020-05-20 15:54:36 -04:00
|
|
|
cb()
|
2020-05-06 06:12:47 -04:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
getDoc(projectId, docId, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.get(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}`,
|
2020-05-06 06:12:47 -04:00
|
|
|
(error, res, body) => {
|
|
|
|
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
|
|
|
body = JSON.parse(body)
|
|
|
|
}
|
2020-05-20 15:54:36 -04:00
|
|
|
callback(error, res, body)
|
2020-05-06 06:12:47 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
getDocAndRecentOps(projectId, docId, fromVersion, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.get(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}`,
|
2020-05-06 06:12:47 -04:00
|
|
|
(error, res, body) => {
|
|
|
|
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
|
|
|
body = JSON.parse(body)
|
|
|
|
}
|
2020-05-20 15:54:36 -04:00
|
|
|
callback(error, res, body)
|
2020-05-06 06:12:47 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
preloadDoc(projectId, docId, callback) {
|
|
|
|
DocUpdaterClient.getDoc(projectId, docId, callback)
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
|
2021-08-02 09:13:38 -04:00
|
|
|
peekDoc(projectId, docId, callback) {
|
|
|
|
request.get(
|
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}/peek`,
|
|
|
|
(error, res, body) => {
|
|
|
|
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
|
|
|
body = JSON.parse(body)
|
|
|
|
}
|
|
|
|
callback(error, res, body)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
flushDoc(projectId, docId, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.post(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}/flush`,
|
2020-05-06 06:12:47 -04:00
|
|
|
(error, res, body) => callback(error, res, body)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
setDocLines(projectId, docId, lines, source, userId, undoing, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.post(
|
2020-05-06 06:12:47 -04:00
|
|
|
{
|
2020-05-20 16:12:27 -04:00
|
|
|
url: `http://localhost:3003/project/${projectId}/doc/${docId}`,
|
2020-05-06 06:12:47 -04:00
|
|
|
json: {
|
|
|
|
lines,
|
|
|
|
source,
|
2020-05-20 16:12:27 -04:00
|
|
|
user_id: userId,
|
2021-07-13 07:04:42 -04:00
|
|
|
undoing,
|
|
|
|
},
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
(error, res, body) => callback(error, res, body)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
deleteDoc(projectId, docId, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.del(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}`,
|
2020-05-06 06:12:47 -04:00
|
|
|
(error, res, body) => callback(error, res, body)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
flushProject(projectId, callback) {
|
|
|
|
request.post(`http://localhost:3003/project/${projectId}/flush`, callback)
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
deleteProject(projectId, callback) {
|
|
|
|
request.del(`http://localhost:3003/project/${projectId}`, callback)
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
deleteProjectOnShutdown(projectId, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.del(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}?background=true&shutdown=true`,
|
2020-05-06 06:12:47 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
flushOldProjects(callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.get(
|
2020-05-06 06:12:47 -04:00
|
|
|
'http://localhost:3003/flush_queued_projects?min_delete_age=1',
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
acceptChange(projectId, docId, changeId, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.post(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}/change/${changeId}/accept`,
|
2020-05-06 06:12:47 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
removeComment(projectId, docId, comment, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.del(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc/${docId}/comment/${comment}`,
|
2020-05-06 06:12:47 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:12:27 -04:00
|
|
|
getProjectDocs(projectId, projectStateHash, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.get(
|
2020-05-20 16:12:27 -04:00
|
|
|
`http://localhost:3003/project/${projectId}/doc?state=${projectStateHash}`,
|
2020-05-06 06:12:47 -04:00
|
|
|
(error, res, body) => {
|
|
|
|
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
|
|
|
body = JSON.parse(body)
|
|
|
|
}
|
2020-05-20 15:54:36 -04:00
|
|
|
callback(error, res, body)
|
2020-05-06 06:12:47 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-05-20 16:26:22 -04:00
|
|
|
sendProjectUpdate(projectId, userId, updates, version, callback) {
|
2020-05-20 15:54:36 -04:00
|
|
|
request.post(
|
2020-05-06 06:12:47 -04:00
|
|
|
{
|
2020-05-20 16:12:27 -04:00
|
|
|
url: `http://localhost:3003/project/${projectId}`,
|
2021-07-13 07:04:42 -04:00
|
|
|
json: { userId, updates, version },
|
2020-05-06 06:12:47 -04:00
|
|
|
},
|
|
|
|
(error, res, body) => callback(error, res, body)
|
|
|
|
)
|
2021-07-13 07:04:42 -04:00
|
|
|
},
|
2020-05-06 06:12:47 -04:00
|
|
|
}
|