import fs from 'fs-extra'; import superagent from 'superagent'; import proxy from 'superagent-proxy'; import Logger from './utils/logger'; import sleep from './utils/sleep'; import * as TIME from './utils/time'; import { apply } from './vjudge'; import path from 'path'; import child from 'child_process'; import htmlspecialchars from './utils/htmlspecialchars'; proxy(superagent); const logger = new Logger('daemon'); interface UOJConfig { server_url: string; judger_name: string; password: string; } interface UOJSubmission { id: number; problem_id: number; problem_mtime: number; content: any; status: string; judge_time: string; } export default async function daemon(config: UOJConfig) { const request = (url: string, data = {}) => superagent .post(`${config.server_url}/judge${url}`) .set('Content-Type', 'application/x-www-form-urlencoded') .send( Object.entries({ judger_name: config.judger_name, password: config.password, ...data, }) .map( ([k, v]) => `${k}=${encodeURIComponent( typeof v === 'string' ? v : JSON.stringify(v) )}` ) .join('&') ); const vjudge = await apply(request); while (true) { try { const { text, error } = await request('/submit'); if (error) { logger.error('/submit', error.message); await sleep(2 * TIME.second); } else if (text.startsWith('Nothing to judge')) { await sleep(TIME.second); } else { const data: UOJSubmission = JSON.parse(text); const { id, content, judge_time } = data; const config = Object.fromEntries(content.config); const tmpdir = `/tmp/s2oj_rmj/${id}/`; if (config.test_sample_only === 'on') { await request('/submit', { submit: 1, fetch_new: 0, id, result: JSON.stringify({ status: 'Judged', score: 100, time: 0, memory: 0, details: 'Sample test is not available.', }), judge_time, }); await sleep(TIME.second); continue; } fs.ensureDirSync(tmpdir); let code = ''; try { // Download source code logger.debug('Downloading source code.', id); const zipFilePath = path.resolve(tmpdir, 'all.zip'); const res = request(`/download${content.file_name}`); const stream = fs.createWriteStream(zipFilePath); res.pipe(stream); await new Promise((resolve, reject) => { stream.on('finish', resolve); stream.on('error', reject); }); // Extract source code logger.debug('Extracting source code.', id); const extractedPath = path.resolve(tmpdir, 'all'); await new Promise((resolve, reject) => { child.exec(`unzip ${zipFilePath} -d ${extractedPath}`, e => { if (e) reject(e); else resolve(true); }); }); // Read source code logger.debug('Reading source code.', id); const sourceCodePath = path.resolve(extractedPath, 'answer.code'); code = fs.readFileSync(sourceCodePath, 'utf-8'); } catch (e) { await request('/submit', { submit: 1, fetch_new: 0, id, result: JSON.stringify({ status: 'Judged', score: 0, error: 'Judgment Failed', details: `Failed to download and extract source code.`, }), judge_time, }); logger.error( 'Failed to download and extract source code.', id, e.message ); fs.removeSync(tmpdir); await sleep(TIME.second); continue; } // Start judging logger.info('Start judging', id, `(problem ${data.problem_id})`); try { await vjudge.judge( id, config.remote_online_judge, config.remote_problem_id, config.answer_language, code, judge_time, config ); } catch (err) { await request('/submit', { submit: 1, fetch_new: 0, id, result: JSON.stringify({ status: 'Judged', score: 0, error: 'Judgment Failed', details: `${htmlspecialchars(err.message)}`, }), judge_time, }); logger.error('Judgment Failed.', id, err.message); fs.removeSync(tmpdir); continue; } fs.removeSync(tmpdir); await sleep(TIME.second); } } catch (err) { logger.error(err.message); await sleep(2 * TIME.second); } } }