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