2020-05-06 06:12:17 -04:00
|
|
|
/*
|
|
|
|
* 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 DocUpdaterClient;
|
|
|
|
const Settings = require('settings-sharelatex');
|
|
|
|
const rclient = require("redis-sharelatex").createClient(Settings.redis.documentupdater);
|
|
|
|
const keys = Settings.redis.documentupdater.key_schema;
|
|
|
|
const request = require("request").defaults({jar: false});
|
|
|
|
const async = require("async");
|
|
|
|
|
|
|
|
const rclient_sub = require("redis-sharelatex").createClient(Settings.redis.pubsub);
|
|
|
|
rclient_sub.subscribe("applied-ops");
|
|
|
|
rclient_sub.setMaxListeners(0);
|
2016-06-21 09:31:20 -04:00
|
|
|
|
2020-05-06 06:12:17 -04:00
|
|
|
module.exports = (DocUpdaterClient = {
|
|
|
|
randomId() {
|
|
|
|
const chars = __range__(1, 24, true).map((i) =>
|
|
|
|
Math.random().toString(16)[2]);
|
|
|
|
return chars.join("");
|
|
|
|
},
|
2016-06-17 07:17:22 -04:00
|
|
|
|
2020-05-06 06:12:17 -04:00
|
|
|
subscribeToAppliedOps(callback) {
|
|
|
|
if (callback == null) { callback = function(message) {}; }
|
|
|
|
return rclient_sub.on("message", callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
sendUpdate(project_id, doc_id, update, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return rclient.rpush(keys.pendingUpdates({doc_id}), JSON.stringify(update), function(error){
|
|
|
|
if (error != null) { return callback(error); }
|
|
|
|
const doc_key = `${project_id}:${doc_id}`;
|
|
|
|
return rclient.sadd("DocsWithPendingUpdates", doc_key, function(error) {
|
|
|
|
if (error != null) { return callback(error); }
|
|
|
|
return rclient.rpush("pending-updates-list", doc_key, callback);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
sendUpdates(project_id, doc_id, updates, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return DocUpdaterClient.preloadDoc(project_id, doc_id, function(error) {
|
|
|
|
if (error != null) { return callback(error); }
|
|
|
|
const jobs = [];
|
|
|
|
for (let update of Array.from(updates)) {
|
|
|
|
((update => jobs.push(callback => DocUpdaterClient.sendUpdate(project_id, doc_id, update, callback))))(update);
|
|
|
|
}
|
|
|
|
return async.series(jobs, err => DocUpdaterClient.waitForPendingUpdates(project_id, doc_id, callback));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
waitForPendingUpdates(project_id, doc_id, callback) {
|
|
|
|
return async.retry({times: 30, interval: 100}, cb => rclient.llen(keys.pendingUpdates({doc_id}), function(err, length) {
|
|
|
|
if (length > 0) {
|
|
|
|
return cb(new Error("updates still pending"));
|
|
|
|
} else {
|
|
|
|
return cb();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
getDoc(project_id, doc_id, callback) {
|
|
|
|
if (callback == null) { callback = function(error, res, body) {}; }
|
|
|
|
return request.get(`http://localhost:3003/project/${project_id}/doc/${doc_id}`, function(error, res, body) {
|
|
|
|
if ((body != null) && (res.statusCode >= 200) && (res.statusCode < 300)) {
|
|
|
|
body = JSON.parse(body);
|
|
|
|
}
|
|
|
|
return callback(error, res, body);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getDocAndRecentOps(project_id, doc_id, fromVersion, callback) {
|
|
|
|
if (callback == null) { callback = function(error, res, body) {}; }
|
|
|
|
return request.get(`http://localhost:3003/project/${project_id}/doc/${doc_id}?fromVersion=${fromVersion}`, function(error, res, body) {
|
|
|
|
if ((body != null) && (res.statusCode >= 200) && (res.statusCode < 300)) {
|
|
|
|
body = JSON.parse(body);
|
|
|
|
}
|
|
|
|
return callback(error, res, body);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
preloadDoc(project_id, doc_id, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return DocUpdaterClient.getDoc(project_id, doc_id, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
flushDoc(project_id, doc_id, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return request.post(`http://localhost:3003/project/${project_id}/doc/${doc_id}/flush`, (error, res, body) => callback(error, res, body));
|
|
|
|
},
|
|
|
|
|
|
|
|
setDocLines(project_id, doc_id, lines, source, user_id, undoing, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return request.post({
|
|
|
|
url: `http://localhost:3003/project/${project_id}/doc/${doc_id}`,
|
|
|
|
json: {
|
|
|
|
lines,
|
|
|
|
source,
|
|
|
|
user_id,
|
|
|
|
undoing
|
|
|
|
}
|
|
|
|
}, (error, res, body) => callback(error, res, body));
|
|
|
|
},
|
|
|
|
|
|
|
|
deleteDoc(project_id, doc_id, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return request.del(`http://localhost:3003/project/${project_id}/doc/${doc_id}`, (error, res, body) => callback(error, res, body));
|
|
|
|
},
|
|
|
|
|
|
|
|
flushProject(project_id, callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.post(`http://localhost:3003/project/${project_id}/flush`, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
deleteProject(project_id, callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.del(`http://localhost:3003/project/${project_id}`, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
deleteProjectOnShutdown(project_id, callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.del(`http://localhost:3003/project/${project_id}?background=true&shutdown=true`, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
flushOldProjects(callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.get("http://localhost:3003/flush_queued_projects?min_delete_age=1", callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
acceptChange(project_id, doc_id, change_id, callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.post(`http://localhost:3003/project/${project_id}/doc/${doc_id}/change/${change_id}/accept`, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
removeComment(project_id, doc_id, comment, callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.del(`http://localhost:3003/project/${project_id}/doc/${doc_id}/comment/${comment}`, callback);
|
|
|
|
},
|
|
|
|
|
|
|
|
getProjectDocs(project_id, projectStateHash, callback) {
|
|
|
|
if (callback == null) { callback = function() {}; }
|
|
|
|
return request.get(`http://localhost:3003/project/${project_id}/doc?state=${projectStateHash}`, function(error, res, body) {
|
|
|
|
if ((body != null) && (res.statusCode >= 200) && (res.statusCode < 300)) {
|
|
|
|
body = JSON.parse(body);
|
|
|
|
}
|
|
|
|
return callback(error, res, body);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
sendProjectUpdate(project_id, userId, docUpdates, fileUpdates, version, callback) {
|
|
|
|
if (callback == null) { callback = function(error) {}; }
|
|
|
|
return request.post({
|
|
|
|
url: `http://localhost:3003/project/${project_id}`,
|
2018-03-06 05:36:38 -05:00
|
|
|
json: { userId, docUpdates, fileUpdates, version }
|
2020-05-06 06:12:17 -04:00
|
|
|
}, (error, res, body) => callback(error, res, body));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function __range__(left, right, inclusive) {
|
|
|
|
let range = [];
|
|
|
|
let ascending = left < right;
|
|
|
|
let end = !inclusive ? right : ascending ? right + 1 : right - 1;
|
|
|
|
for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
|
|
|
|
range.push(i);
|
|
|
|
}
|
|
|
|
return range;
|
|
|
|
}
|