|
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
const socketio = require("socket.io");
|
|
|
|
const diff = require("jsondiffpatch");
|
|
|
|
const jwt = require("jsonwebtoken");
|
|
|
|
const winston = require("winston");
|
|
|
|
const judgeResult = require("../libs/judgeResult");
|
|
|
|
const interfaces = require("../libs/judger_interfaces");
|
|
|
|
let ioInstance;
|
|
|
|
let detailProgressNamespace;
|
|
|
|
let roughProgressNamespace;
|
|
|
|
let compileProgressNamespace;
|
|
|
|
const currentJudgeList = {};
|
|
|
|
const finishedJudgeList = {};
|
|
|
|
const compiledList = [];
|
|
|
|
const clientDetailProgressList = {};
|
|
|
|
const clientDisplayConfigList = {};
|
|
|
|
|
|
|
|
function processOverallResult(source, config) {
|
|
|
|
if (source == null)
|
|
|
|
return null;
|
|
|
|
if (source.error != null) {
|
|
|
|
return {
|
|
|
|
error: source.error,
|
|
|
|
systemMessage: source.systemMessage
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
compile: source.compile,
|
|
|
|
judge: config.showDetailResult ? (source.judge && {
|
|
|
|
subtasks: source.judge.subtasks && source.judge.subtasks.map(st => ({
|
|
|
|
score: st.score,
|
|
|
|
cases: st.cases.map(cs => ({
|
|
|
|
status: cs.status,
|
|
|
|
result: cs.result && {
|
|
|
|
type: cs.result.type,
|
|
|
|
time: config.showUsage ? cs.result.time : undefined,
|
|
|
|
memory: config.showUsage ? cs.result.memory : undefined,
|
|
|
|
scoringRate: cs.result.scoringRate,
|
|
|
|
systemMessage: cs.result.systemMessage,
|
|
|
|
input: config.showTestdata ? cs.result.input : undefined,
|
|
|
|
output: config.showTestdata ? cs.result.output : undefined,
|
|
|
|
userOutput: config.showTestdata ? cs.result.userOutput : undefined,
|
|
|
|
userError: config.showTestdata ? cs.result.userError : undefined,
|
|
|
|
spjMessage: config.showTestdata ? cs.result.spjMessage : undefined,
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}))
|
|
|
|
}) : null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
function getCompileStatus(status) {
|
|
|
|
if (["System Error", "Compile Error", "No Testdata"].includes(status)) {
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return "Submitted";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function processRoughResult(source, config) {
|
|
|
|
const result = config.showResult ?
|
|
|
|
source.result :
|
|
|
|
getCompileStatus(source.result);
|
|
|
|
return {
|
|
|
|
result: result,
|
|
|
|
time: config.showUsage ? source.time : null,
|
|
|
|
memory: config.showUsage ? source.memory : null,
|
|
|
|
score: config.showScore ? source.score : null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
function forAllClients(ns, taskId, exec) {
|
|
|
|
ns.in(taskId.toString()).clients((err, clients) => {
|
|
|
|
if (!err) {
|
|
|
|
clients.forEach(client => {
|
|
|
|
exec(client);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
winston.warn(`Error while listing socketio clients in ${taskId}`, err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function initializeSocketIO(s) {
|
|
|
|
ioInstance = socketio(s);
|
|
|
|
const initializeNamespace = (name, exec) => {
|
|
|
|
winston.debug('initializing socketIO', name);
|
|
|
|
const newNamespace = ioInstance.of('/' + name);
|
|
|
|
newNamespace.on('connection', (socket) => {
|
|
|
|
socket.on('disconnect', () => {
|
|
|
|
winston.info(`Client ${socket.id} disconnected.`);
|
|
|
|
delete clientDisplayConfigList[socket.id];
|
|
|
|
if (clientDetailProgressList[socket.id]) {
|
|
|
|
delete clientDetailProgressList[socket.id];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
socket.on('join', (reqJwt, cb) => {
|
|
|
|
winston.info(`Client ${socket.id} connected.`);
|
|
|
|
let req;
|
|
|
|
try {
|
|
|
|
req = jwt.verify(reqJwt, syzoj.config.session_secret);
|
|
|
|
if (req.type !== name) {
|
|
|
|
throw new Error("Request type in token mismatch.");
|
|
|
|
}
|
|
|
|
clientDisplayConfigList[socket.id] = req.displayConfig;
|
|
|
|
const taskId = req.taskId;
|
|
|
|
winston.verbose(`A client trying to join ${name} namespace for ${taskId}.`);
|
|
|
|
socket.join(taskId.toString());
|
|
|
|
exec(req, socket).then(x => cb(x), err => cb({ ok: false, message: err.toString() }));
|
|
|
|
}
|
|
|
|
catch (err) {
|
|
|
|
winston.info('Error while joining.');
|
|
|
|
cb({
|
|
|
|
ok: false,
|
|
|
|
message: err.toString()
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return newNamespace;
|
|
|
|
};
|
|
|
|
detailProgressNamespace = initializeNamespace('detail', async (req, socket) => {
|
|
|
|
const taskId = req.taskId;
|
|
|
|
if (finishedJudgeList[taskId]) {
|
|
|
|
winston.debug(`Judge task #${taskId} has been finished, ${JSON.stringify(currentJudgeList[taskId])}`);
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: false,
|
|
|
|
finished: true,
|
|
|
|
result: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[socket.id]),
|
|
|
|
roughResult: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[socket.id])
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
winston.debug(`Judge task #${taskId} has not been finished`);
|
|
|
|
if (currentJudgeList[taskId]) {
|
|
|
|
clientDetailProgressList[socket.id] = {
|
|
|
|
version: 0,
|
|
|
|
content: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[socket.id])
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
finished: false,
|
|
|
|
running: true,
|
|
|
|
current: clientDetailProgressList[socket.id]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
finished: false,
|
|
|
|
running: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
roughProgressNamespace = initializeNamespace('rough', async (req, socket) => {
|
|
|
|
const taskId = req.taskId;
|
|
|
|
if (finishedJudgeList[taskId]) {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: false,
|
|
|
|
finished: true,
|
|
|
|
result: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[socket.id])
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (currentJudgeList[taskId]) {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: true,
|
|
|
|
finished: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: false,
|
|
|
|
finished: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
compileProgressNamespace = initializeNamespace('compile', async (req, socket) => {
|
|
|
|
const taskId = req.taskId;
|
|
|
|
if (compiledList[taskId]) {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: false,
|
|
|
|
finished: true,
|
|
|
|
result: compiledList[taskId]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (currentJudgeList[taskId]) {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: true,
|
|
|
|
finished: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return {
|
|
|
|
ok: true,
|
|
|
|
running: false,
|
|
|
|
finished: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
exports.initializeSocketIO = initializeSocketIO;
|
|
|
|
function createTask(taskId) {
|
|
|
|
winston.debug(`Judge task #${taskId} has started`);
|
|
|
|
currentJudgeList[taskId] = {};
|
|
|
|
finishedJudgeList[taskId] = null;
|
|
|
|
forAllClients(detailProgressNamespace, taskId, (clientId) => {
|
|
|
|
clientDetailProgressList[clientId] = {
|
|
|
|
version: 0,
|
|
|
|
content: {}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
roughProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
|
|
|
|
detailProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
|
|
|
|
compileProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
|
|
|
|
}
|
|
|
|
exports.createTask = createTask;
|
|
|
|
function updateCompileStatus(taskId, result) {
|
|
|
|
winston.debug(`Updating compilation status for #${taskId}`);
|
|
|
|
compiledList[taskId] = { result: result.status === interfaces.TaskStatus.Done ? 'Submitted' : 'Compile Error' };
|
|
|
|
compileProgressNamespace.to(taskId.toString()).emit('finish', {
|
|
|
|
taskId: taskId,
|
|
|
|
result: compiledList[taskId]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
exports.updateCompileStatus = updateCompileStatus;
|
|
|
|
function updateProgress(taskId, data) {
|
|
|
|
winston.verbose(`Updating progress for #${taskId}`);
|
|
|
|
currentJudgeList[taskId] = data;
|
|
|
|
forAllClients(detailProgressNamespace, taskId, (client) => {
|
|
|
|
winston.debug(`Pushing progress update to ${client}`);
|
|
|
|
if (clientDetailProgressList[client] && clientDisplayConfigList[client]) {
|
|
|
|
const original = clientDetailProgressList[client].content;
|
|
|
|
const updated = processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[client]);
|
|
|
|
const version = clientDetailProgressList[client].version;
|
|
|
|
detailProgressNamespace.sockets[client].emit('update', {
|
|
|
|
taskId: taskId,
|
|
|
|
from: version,
|
|
|
|
to: version + 1,
|
|
|
|
delta: diff.diff(original, updated)
|
|
|
|
});
|
|
|
|
clientDetailProgressList[client].version++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
exports.updateProgress = updateProgress;
|
|
|
|
function updateResult(taskId, data) {
|
|
|
|
currentJudgeList[taskId] = data;
|
|
|
|
if (compiledList[taskId] == null) {
|
|
|
|
if (data.error != null) {
|
|
|
|
compiledList[taskId] = { result: "System Error" };
|
|
|
|
compileProgressNamespace.to(taskId.toString()).emit('finish', {
|
|
|
|
taskId: taskId,
|
|
|
|
result: compiledList[taskId]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const finalResult = judgeResult.convertResult(taskId, data);
|
|
|
|
const roughResult = {
|
|
|
|
result: finalResult.statusString,
|
|
|
|
time: finalResult.time,
|
|
|
|
memory: finalResult.memory,
|
|
|
|
score: finalResult.score
|
|
|
|
};
|
|
|
|
finishedJudgeList[taskId] = roughResult;
|
|
|
|
forAllClients(roughProgressNamespace, taskId, (client) => {
|
|
|
|
winston.debug(`Pushing rough result to ${client}`);
|
|
|
|
roughProgressNamespace.sockets[client].emit('finish', {
|
|
|
|
taskId: taskId,
|
|
|
|
result: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[client])
|
|
|
|
});
|
|
|
|
});
|
|
|
|
forAllClients(detailProgressNamespace, taskId, (client) => {
|
|
|
|
if (clientDisplayConfigList[client]) {
|
|
|
|
winston.debug(`Pushing detail result to ${client}`);
|
|
|
|
detailProgressNamespace.sockets[client].emit('finish', {
|
|
|
|
taskId: taskId,
|
|
|
|
result: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[client]),
|
|
|
|
roughResult: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[client])
|
|
|
|
});
|
|
|
|
delete clientDetailProgressList[client];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
exports.updateResult = updateResult;
|
|
|
|
function cleanupProgress(taskId) {
|
|
|
|
setTimeout(() => { delete currentJudgeList[taskId]; }, 10000);
|
|
|
|
}
|
|
|
|
exports.cleanupProgress = cleanupProgress;
|
|
|
|
//# sourceMappingURL=socketio.js.map
|
|
|
|
|
|
|
|
initializeSocketIO(app.server);
|