diff --git a/packages/hydrooj/src/context.ts b/packages/hydrooj/src/context.ts index 89b70fd42..f169a63e6 100644 --- a/packages/hydrooj/src/context.ts +++ b/packages/hydrooj/src/context.ts @@ -6,6 +6,7 @@ import { inject } from './lib/ui'; import { Loader } from './loader'; import type { EventMap } from './service/bus'; import type { CheckService } from './service/check'; +import type { } from './service/migration'; import type { ConnectionHandler, Handler } from './service/server'; export interface Events extends cordis.Events, EventMap, ServerEvents { } diff --git a/packages/hydrooj/src/service/migration.ts b/packages/hydrooj/src/service/migration.ts index d2bd8384a..922a69e70 100644 --- a/packages/hydrooj/src/service/migration.ts +++ b/packages/hydrooj/src/service/migration.ts @@ -25,6 +25,11 @@ export default class MigrationService extends Service { this.channels[name] = s; } + dontWait(func: () => Promise, name: string) { + (func as any).dontWait = name; + return func; + } + async doUpgrade() { this.called = true; for (const channel in this.channels) { @@ -45,8 +50,16 @@ export default class MigrationService extends Service { continue; } logger.info('Upgrading database [%s]: from %d to %d', channel, dbVer, expected); - const result = await func(); - if (!result) break; + if ('dontWait' in func) { + logger.info('[Background Task]'); + // For those scripts we don't really care if they fail + func().then(() => { + logger.info('Background Task Completed [%s]: from %d to %d', channel, func.dontWait, expected); + }).catch(logger.error); + } else { + const result = await func(); + if (!result) break; + } dbVer++; await system.set(name, dbVer); } diff --git a/packages/vjudge/src/index.ts b/packages/vjudge/src/index.ts index 28445e998..14660a2af 100644 --- a/packages/vjudge/src/index.ts +++ b/packages/vjudge/src/index.ts @@ -1,8 +1,9 @@ /* eslint-disable no-await-in-loop */ import os from 'os'; +import { LangConfig } from '@hydrooj/utils/lib/lang'; import { - Context, db, DomainModel, JudgeHandler, Logger, - ProblemModel, RecordModel, Service, SettingModel, sleep, STATUS, TaskModel, Time, + Context, db, DomainModel, JudgeHandler, Logger, ProblemModel, RecordModel, Service, SettingModel, + sleep, STATUS, SystemModel, TaskModel, Time, yaml, } from 'hydrooj'; import { BasicProvider, IBasicProvider, RemoteAccount } from './interface'; import providers from './providers/index'; @@ -48,6 +49,10 @@ class AccountService { await next({ status: STATUS.STATUS_FETCHED }); try { const langConfig = SettingModel.langs[task.lang]; + if (this.Provider.Langs && !langConfig?.validAs?.[this.account.type]) { + end({ status: STATUS.STATUS_COMPILE_ERROR, message: `Language not supported: ${task.lang}` }); + return; + } if (langConfig.validAs?.[this.account.type]) task.lang = langConfig.validAs[this.account.type]; const comment = langConfig.comment; if (comment) { @@ -187,11 +192,29 @@ class VJudgeService extends Service { if (account.enableOn && !account.enableOn.includes(os.hostname())) continue; this.pool[`${account.type}/${account.handle}`] = new AccountService(provider, account); } + // FIXME: potential race condition + if (provider.Langs) this.updateLangs(type, provider.Langs); this[Context.current]?.on('dispose', () => { // TODO dispose session }); } + async updateLangs(provider: string, mapping: Record>) { + const config = yaml.load(SystemModel.get('hydrooj.langs')); + const old = yaml.dump(config); + for (const key in mapping) { + config[key] ||= { + execute: '/bin/echo For remote judge only', + hidden: true, + ...mapping[key], + }; + config[key].validAs ||= {}; + config[key].validAs[provider] = mapping[key].key; + } + const newConfig = yaml.dump(config); + if (old !== newConfig) await SystemModel.set('hydrooj.langs', newConfig); + } + async sync() { for (const key in this.pool) { const account = this.pool[key]; @@ -222,6 +245,22 @@ export async function apply(ctx: Context) { if (process.env.NODE_APP_INSTANCE !== '0') return; if (process.env.HYDRO_CLI) return; ctx.plugin(VJudgeService); + ctx.inject(['migration'], async (c) => { + c.migration.registerChannel('vjudge', [ + async function init() { }, // eslint-disable-line + c.migration.dontWait(async () => { + const rewrite = (from: string[], to: string) => RecordModel.coll.updateMany({ lang: { $in: from } }, { $set: { lang: to } }); + await Promise.all([ + rewrite(['csgoj.0', 'poj.1', 'poj.5'], 'c'), + rewrite(['csgoj.1'], 'cc.cc17o2'), + rewrite(['csgoj.3', 'poj.2'], 'java'), + rewrite(['csgoj.6'], 'py.py3'), + rewrite(['csgoj.17'], 'go'), + rewrite(['poj.0', 'poj.4'], 'cc.cc98'), + ]); + }, 'update csgoj and poj langs in record collection'), + ]); + }); ctx.inject(['vjudge'], async (c) => { await c.vjudge.start(); for (const [k, v] of Object.entries(providers)) { diff --git a/packages/vjudge/src/interface.ts b/packages/vjudge/src/interface.ts index 4886324a9..e852378d3 100644 --- a/packages/vjudge/src/interface.ts +++ b/packages/vjudge/src/interface.ts @@ -1,3 +1,4 @@ +import { LangConfig } from '@hydrooj/utils/lib/lang'; import { JudgeResultBody } from 'hydrooj'; export interface RemoteAccount { @@ -43,5 +44,6 @@ export interface IBasicProvider { } export interface BasicProvider { - new(account: RemoteAccount, save: (data: any) => Promise): IBasicProvider + new(account: RemoteAccount, save: (data: any) => Promise): IBasicProvider; + Langs?: Record>; } diff --git a/packages/vjudge/src/providers/csgoj.ts b/packages/vjudge/src/providers/csgoj.ts index ed6aa460a..5c4228aeb 100644 --- a/packages/vjudge/src/providers/csgoj.ts +++ b/packages/vjudge/src/providers/csgoj.ts @@ -21,40 +21,32 @@ const statusDict = { 11: STATUS.STATUS_COMPILE_ERROR, }; -/* langs -csgoj: - display: CsgOJ - execute: /bin/echo Invalid - hidden: true - remote: csgoj -csgoj.0: - display: C - monaco: c - highlight: c astyle-c - comment: // -csgoj.1: - display: C++ - monaco: cpp - highlight: cpp astyle-c - comment: // -csgoj.3: - display: Java - monaco: java - highlight: java astyle-java - comment: // -csgoj.6: - display: Python3 - highlight: python - comment: '#' -csgoj.17: - display: Go - highlight: go - comment: // -*/ - const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0'; export default class CSGOJProvider extends BasicFetcher implements IBasicProvider { + static Langs = { + c: { + display: 'C', + key: '0', + }, + 'cc.cc17o2': { + display: 'C++', + key: '1', + }, + java: { + display: 'Java', + key: '3', + }, + 'py.py3': { + display: 'Python3', + key: '6', + }, + go: { + display: 'Go', + key: '17', + }, + }; + constructor(public account: RemoteAccount, private save: (data: any) => Promise) { super(account, 'https://cpc.csgrandeur.cn', 'form', logger, { headers: { 'User-Agent': userAgent }, @@ -139,17 +131,13 @@ export default class CSGOJProvider extends BasicFetcher implements IBasicProvide return result.body.rows.map((i) => `P${+i.problem_id}`); } - async submitProblem(id: string, lang: string, source: string, info, next, end) { + async submitProblem(id: string, lang: string, source: string) { await this.ensureLogin(); - if (!lang.includes('csgoj')) { - end({ status: STATUS.STATUS_COMPILE_ERROR, message: `Language not supported: ${lang}` }); - return null; - } const result = await this.post('/csgoj/Problemset/submit_ajax') .set('referer', `https://cpc.csgrandeur.cn/csgoj/problemset/submit?pid=${id.split('P')[1]}`) .send({ pid: id.split('P')[1], - language: lang.split('csgoj.')[1], + language: lang, source, }); return result.body.data.solution_id; diff --git a/packages/vjudge/src/providers/hduoj.ts b/packages/vjudge/src/providers/hduoj.ts index d2a9c8b8b..73012a570 100644 --- a/packages/vjudge/src/providers/hduoj.ts +++ b/packages/vjudge/src/providers/hduoj.ts @@ -9,6 +9,7 @@ import { } from 'hydrooj'; import { BasicFetcher } from '../fetch'; import { IBasicProvider, RemoteAccount } from '../interface'; +import { VERDICT } from '../verdict'; declare module 'superagent' { interface Request { @@ -20,20 +21,6 @@ charset(superagent); proxy(superagent as any); const logger = new Logger('remote/hduoj'); -const StatusMapping = { - Queuing: STATUS.STATUS_WAITING, - Running: STATUS.STATUS_JUDGING, - Compiling: STATUS.STATUS_COMPILING, - Accepted: STATUS.STATUS_ACCEPTED, - 'Presentation Error': STATUS.STATUS_WRONG_ANSWER, - 'Runtime Error': STATUS.STATUS_RUNTIME_ERROR, - 'Output Limit Exceeded': STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED, - 'Wrong Answer': STATUS.STATUS_WRONG_ANSWER, - 'Compilation Error': STATUS.STATUS_COMPILE_ERROR, - 'Memory Limit Exceeded': STATUS.STATUS_MEMORY_LIMIT_EXCEEDED, - 'Time Limit Exceeded': STATUS.STATUS_TIME_LIMIT_EXCEEDED, -}; - export default class HDUOJProvider extends BasicFetcher implements IBasicProvider { constructor(public account: RemoteAccount, private save: (data: any) => Promise) { super(account, 'https://acm.hdu.edu.cn', 'form', logger); @@ -189,7 +176,7 @@ export default class HDUOJProvider extends BasicFetcher implements IBasicProvide const { text } = await this.get(`/status.php?first=${id}`); const { window: { document } } = new JSDOM(text); const submission = document.querySelector('#fixed_table>table>tbody').children[2]; - const status = StatusMapping[submission.children[2].children[0].textContent.trim()] + const status = VERDICT[submission.children[2].children[0].textContent.trim()] || STATUS.STATUS_SYSTEM_ERROR; if (status === STATUS.STATUS_JUDGING) continue; if (status === STATUS.STATUS_COMPILE_ERROR) { diff --git a/packages/vjudge/src/providers/poj.ts b/packages/vjudge/src/providers/poj.ts index e8bce7c09..4b98b6932 100644 --- a/packages/vjudge/src/providers/poj.ts +++ b/packages/vjudge/src/providers/poj.ts @@ -10,54 +10,31 @@ import { VERDICT } from '../verdict'; const logger = new Logger('remote/poj'); -/* langs -poj: - display: POJ - execute: /bin/echo Invalid - domain: - - poj -poj.0: - display: G++ - monaco: cpp - highlight: cpp astyle-c - comment: // -poj.1: - display: GCC - monaco: c - highlight: c astyle-c - comment: // -poj.2: - display: Java - monaco: java - highlight: java astyle-java - comment: // -poj.3: - display: Pascal - monaco: pascal - highlight: pascal - comment: // -poj.4: - display: C++ - monaco: cpp - highlight: cpp astyle-c - comment: // -poj.5: - display: C - monaco: c - highlight: c astyle-c - comment: // -poj.6: - display: Fortran - monaco: plain - highlight: plain -*/ - const langs = { default: 'en', 'zh-CN': 'zh', }; export default class POJProvider extends BasicFetcher implements IBasicProvider { + static Langs = { + 'cc.cc98': { + display: 'C++', + key: '0', + }, + c: { + display: 'C', + key: '1', + }, + java: { + display: 'Java', + key: '2', + }, + pas: { + display: 'Pascal', + key: '3', + }, + }; + constructor(public account: RemoteAccount, private save: (data: any) => Promise) { super(account, 'http://poj.org', 'form', logger); } @@ -192,9 +169,8 @@ export default class POJProvider extends BasicFetcher implements IBasicProvider .map((i) => `P${+i.children[0].innerHTML ? i.children[0].innerHTML : i.children[1].innerHTML}`); } - async submitProblem(id: string, lang: string, code: string) { + async submitProblem(id: string, language: string, code: string) { await this.ensureLogin(); - const language = lang.includes('poj.') ? lang.split('poj.')[1] : '0'; code = Buffer.from(code).toString('base64'); const { text } = await this.post('/submit').send({ problem_id: id.split('P')[1], diff --git a/packages/vjudge/src/verdict.ts b/packages/vjudge/src/verdict.ts index 32bfac51b..39711bf66 100644 --- a/packages/vjudge/src/verdict.ts +++ b/packages/vjudge/src/verdict.ts @@ -18,6 +18,8 @@ export const VERDICT = new Proxy>({ EXTRA_TEST_PASSED: STATUS.STATUS_ACCEPTED, COMPILE_ERROR: STATUS.STATUS_COMPILE_ERROR, 'RUNNING_&_JUDGING': STATUS.STATUS_JUDGING, + QUEUING: STATUS.STATUS_WAITING, + RUNNING: STATUS.STATUS_JUDGING, // Codeforces 'HAPPY_NEW_YEAR!': STATUS.STATUS_ACCEPTED,