import fs from 'fs-extra'; import superagent from 'superagent'; import path from 'node:path'; import child from 'node:child_process'; import Luogu, { getAccountInfoFromEnv } from './luogu.js'; import Logger from './utils/logger.js'; import sleep from './utils/sleep.js'; import * as TIME from './utils/time.js'; import htmlspecialchars from './utils/htmlspecialchars.js'; const logger = new Logger('daemon'); async function daemon(config) { function request(url, data) { const req_url = `${config.server_url}/judge${url}`; logger.debug('request', req_url, data); return superagent .post(req_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 luogu = new Luogu(getAccountInfoFromEnv()); logger.info('Daemon started.'); while (true) { try { await sleep(TIME.second); const { text, error } = await request('/submit'); if (error) { logger.error('/submit', error.message); continue; } if (text.startsWith('Nothing to judge')) { logger.debug('Nothing to judge.'); continue; } const data = 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, }); continue; } fs.ensureDirSync(tmpdir); let code = ''; try { // ========================= // Download source code // ========================= logger.debug('Downloading source code for ' + 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 for ' + 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: 'Judgement 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); continue; } // ========================= // Start judging // ========================= logger.info('Start judging', id, `(problem ${data.problem_id})`); try { await luogu.judge(id, config.luogu_pid, config.answer_language, code, judge_time, config, request); } catch (err) { await request('/submit', { submit: 1, fetch_new: 0, id, result: JSON.stringify({ status: 'Judged', score: 0, error: 'Judgement Failed', details: `${htmlspecialchars(err.message)}`, }), judge_time, }); logger.error('Judgement Failed.', id, err.message); fs.removeSync(tmpdir); continue; } fs.removeSync(tmpdir); } catch (err) { logger.error(err.message); } } } export default daemon;