From 4a33816a371e2db68c86b2aca1e18d56cf4363aa Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 7 Feb 2025 15:05:51 +0800 Subject: [PATCH] move delarations to @hydrooj/common --- framework/framework/server.ts | 10 +- packages/common/cases.ts | 216 ++++++++++++++++++ packages/common/index.ts | 4 + packages/{utils/lib => common}/lang.ts | 0 packages/common/package.json | 8 + packages/{utils/lib => common}/status.ts | 10 + .../{utils => common}/tests/subtask.spec.ts | 2 +- packages/common/types.ts | 166 ++++++++++++++ packages/hydrojudge/src/cases.ts | 9 +- packages/hydrojudge/src/checkers.ts | 2 +- packages/hydrojudge/src/compile.ts | 3 +- packages/hydrojudge/src/error.ts | 2 +- packages/hydrojudge/src/flow.ts | 6 +- packages/hydrojudge/src/hosts/builtin.ts | 6 +- packages/hydrojudge/src/hosts/vj4.ts | 4 +- packages/hydrojudge/src/info.ts | 2 +- packages/hydrojudge/src/interface.ts | 12 +- .../hydrojudge/src/judge/communication.ts | 3 +- packages/hydrojudge/src/judge/default.ts | 3 +- packages/hydrojudge/src/judge/generate.ts | 2 +- packages/hydrojudge/src/judge/hack.ts | 7 +- packages/hydrojudge/src/judge/interactive.ts | 3 +- packages/hydrojudge/src/judge/interface.ts | 2 +- packages/hydrojudge/src/judge/objective.ts | 2 +- packages/hydrojudge/src/judge/run.ts | 4 +- .../hydrojudge/src/judge/submit_answer.ts | 3 +- packages/hydrojudge/src/sandbox.ts | 2 +- packages/hydrojudge/src/task.ts | 7 +- packages/hydrojudge/src/testlib.ts | 2 +- packages/hydrooj/src/handler/judge.ts | 11 +- packages/hydrooj/src/handler/manage.ts | 4 +- packages/hydrooj/src/interface.ts | 177 ++------------ packages/hydrooj/src/lib/testdataConfig.ts | 8 +- packages/hydrooj/src/model/builtin.ts | 4 +- packages/hydrooj/src/model/problem.ts | 3 +- packages/hydrooj/src/model/record.ts | 5 +- packages/hydrooj/src/model/setting.ts | 4 +- packages/ui-default/backendlib/template.ts | 2 +- .../problemconfig/ProblemConfigTree.tsx | 2 +- .../problemconfig/tree/AddTestcase.tsx | 2 +- .../components/scratchpad/reducers/pretest.ts | 2 +- .../ui-default/pages/problem_config.page.tsx | 3 +- .../ui-default/pages/problem_files.page.tsx | 2 +- .../pages/problem_statistics.page.ts | 2 +- packages/utils/lib/cases.ts | 60 ----- packages/utils/lib/common.ts | 156 ------------- packages/vjudge/src/index.ts | 7 +- packages/vjudge/src/interface.ts | 3 +- packages/vjudge/src/verdict.ts | 2 +- 49 files changed, 496 insertions(+), 465 deletions(-) create mode 100644 packages/common/cases.ts create mode 100644 packages/common/index.ts rename packages/{utils/lib => common}/lang.ts (100%) create mode 100644 packages/common/package.json rename packages/{utils/lib => common}/status.ts (93%) rename packages/{utils => common}/tests/subtask.spec.ts (97%) create mode 100644 packages/common/types.ts delete mode 100644 packages/utils/lib/cases.ts diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 2eb45920a..455a8809d 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -8,7 +8,7 @@ import Koa from 'koa'; import Body from 'koa-body'; import Compress from 'koa-compress'; import { Shorty } from 'shorty.js'; -import { WebSocketServer } from 'ws'; +import { WebSocket, WebSocketServer } from 'ws'; import { Counter, errorMessage, isClass, Logger, parseMemoryMB, } from '@hydrooj/utils/lib/utils'; @@ -71,6 +71,7 @@ export interface HydroResponse { etag?: string; attachment: (name: string, stream?: any) => void; addHeader: (name: string, value: string) => void; + serializer: typeof serializer; } export type RendererFunction = (name: string, context: Record) => string | Promise; type HydroContext = { @@ -170,7 +171,7 @@ export class HandlerCommon { renderHTML(templateName: string, args: Record) { const type = templateName.split('.')[1]; - const engine = this.ctx.server.renderers[type] || (() => JSON.stringify(args, serializer(false, this))); + const engine = this.ctx.server.renderers[type] || (() => JSON.stringify(args, this.response.serializer(false, this))); return engine(templateName, { handler: this, UserContext: this.user, @@ -265,7 +266,7 @@ export class ConnectionHandler extends HandlerCommon { } send(data: any) { - let payload = JSON.stringify(data, serializer(false, this)); + let payload = JSON.stringify(data, this.response.serializer(false, this)); if (this.compression) { if (this.counter > 1000) this.resetCompression(); payload = this.compression.deflate(payload); @@ -327,6 +328,7 @@ export interface WebServiceConfig { host?: string; xff?: string; xhost?: string; + serializer?: typeof serializer; } export class WebService extends Service { @@ -441,7 +443,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); func: (t) => this.handleHttp(t, NotFoundHandler, () => true), }, ])); - this.addLayer('base', base(logger, this.config.xff, this.config.xhost)); + this.addLayer('base', base(logger, this.config.xff, this.config.xhost, this.config.serializer)); wsServer.on('connection', async (socket, request) => { socket.on('error', (err) => { logger.warn('Websocket Error: %s', err.message); diff --git a/packages/common/cases.ts b/packages/common/cases.ts new file mode 100644 index 000000000..2ca1e7e5d --- /dev/null +++ b/packages/common/cases.ts @@ -0,0 +1,216 @@ +import { findFileSync, parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export async function readYamlCases(cfg: Record = {}, checkFile = (s: string, errMsg: string) => s) { + const config: any = { + checker_type: cfg.checker_type || 'default', + judge_extra_files: [], + user_extra_files: [], + }; + if (cfg.type === 'objective') { + if (cfg.checker || cfg.interactor || cfg.validator) { + throw new Error('You cannot use checker, interactor or validator for objective questions'); + } + } else { + if (cfg.checker) { + if (!cfg.checker.includes('.')) { + config.checker = findFileSync(`@hydrooj/hydrojudge/vendor/testlib/checkers/${cfg.checker}.cpp`, false); + } + config.checker ||= checkFile(cfg.checker, 'Cannot find checker {0}.'); + } + if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.'); + if (cfg.manager) config.manager = checkFile(cfg.manager, 'Cannot find Manager {0}.'); + if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.'); + for (const n of ['judge', 'user']) { + const conf = cfg[`${n}_extra_files`]; + if (!conf) continue; + if (conf instanceof Array) { + config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`)); + } else throw new Error(`Invalid ${n}_extra_files config.`); + } + } + if (cfg.cases?.length) { + config.subtasks = [{ + cases: cfg.cases, + type: 'sum', + }]; + } + if (cfg.time) config.time = parseTimeMS(cfg.time); + if (cfg.memory) config.memory = parseMemoryMB(cfg.memory); + return Object.assign(cfg, config); +} + +export function convertIniConfig(ini: string) { + const f = ini.split('\n'); + const count = parseInt(f[0], 10); + const res = { subtasks: [] }; + for (let i = 1; i <= count; i++) { + if (!f[i]?.trim()) throw new Error('Testdata count incorrect.'); + const [input, output, time, score, memory] = f[i].split('|'); + const cur = { + cases: [{ input, output }], + score: parseInt(score, 10), + time: `${time}s`, + memory: '256m', + }; + if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; + res.subtasks.push(cur); + } + return res; +} + +interface MatchRule { + regex: RegExp; + output: (a: RegExpExecArray) => string[]; + id: (a: RegExpExecArray) => number; + subtask: (a: RegExpExecArray) => number; + preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum'; +} + +const SubtaskMatcher: MatchRule[] = [ + { + regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/, + output: (a) => ['out', 'ans'] + .flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`]) + .flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]) + .concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null), + id: (a) => +a[4], + subtask: (a) => +(a[3] || 1), + preferredScorerType: (a) => (a[3] ? 'min' : 'sum'), + }, + { + regex: /^([^\d]*)\.(in|IN)(\d+)$/, + output: (a) => [ + `${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`, + `${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`, + ].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]), + id: (a) => +a[2], + subtask: () => 1, + preferredScorerType: () => 'sum', + }, + { + regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/, + output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`), + id: (a) => +a[4], + subtask: (a) => +a[2], + preferredScorerType: () => 'min', + }, + { + regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/, + output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`), + id: (a) => +a[2], + subtask: () => 1, + preferredScorerType: () => 'sum', + }, +]; + +function* getScore(totalScore: number, count: number) { + const base = Math.floor(totalScore / count); + const extra = count - (totalScore % count); + for (let i = 0; i < count; i++) { + if (i >= extra) yield base + 1; + else yield base; + } +} + +interface ParsedCase { + id?: number; + time?: number | string; + memory?: number | string; + score?: number; + input?: string; + output?: string; +} +interface ParsedSubtask { + cases: ParsedCase[]; + type: 'min' | 'max' | 'sum'; + time?: number | string; + memory?: number | string; + score?: number; + id?: number; + if?: number[]; +} + +export function readSubtasksFromFiles(files: string[], config) { + const subtask: Record = {}; + for (const s of config.subtasks || []) if (s.id) subtask[s.id] = s; + for (const file of files) { + let match = false; + for (const rule of SubtaskMatcher) { + const data = rule.regex.exec(file); + if (!data) continue; + const sid = rule.subtask(data); + const c = { input: file, output: '', id: rule.id(data) }; + const type = rule.preferredScorerType(data); + const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i); + for (const output of outputs) { + if (output === file) continue; + if (output === '/dev/null' || files.includes(output)) { + match = true; + c.output = output; + if (!subtask[sid]) { + subtask[sid] = { + time: config.time, + memory: config.memory, + type, + cases: [c], + id: sid, + }; + } else if (!subtask[sid].cases) subtask[sid].cases = [c]; + else subtask[sid].cases.push(c); + break; + } + } + if (match) break; + } + } + return Object.values(subtask); +} + +export interface NormalizedCase extends Required { + time: number; + memory: number; +} +export interface NormalizedSubtask extends Required { + cases: NormalizedCase[]; + time: number; + memory: number; +} + +export function normalizeSubtasks( + subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string, + time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false, + timeRate = 1, memoryRate = 1, +): NormalizedSubtask[] { + subtasks.sort((a, b) => (a.id - b.id)); + const subtaskScore = getScore( + Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0), + subtasks.filter((i) => !i.score).length, + ); + return subtasks.map((s, id) => { + s.cases.sort((a, b) => (a.id - b.id)); + const score = s.score || subtaskScore.next().value as number; + const caseScore = getScore( + Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0), + s.cases.filter((i) => !i.score).length, + ); + return { + id: id + 1, + type: 'min', + if: [], + ...s, + score, + time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate, + memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate, + cases: s.cases.map((c, index) => ({ + id: index + 1, + ...c, + score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score), + time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate, + memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate, + input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', + output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', + })) as NormalizedCase[], + }; + }); +} diff --git a/packages/common/index.ts b/packages/common/index.ts new file mode 100644 index 000000000..457305d8a --- /dev/null +++ b/packages/common/index.ts @@ -0,0 +1,4 @@ +export * from './lang'; +export * from './status'; +export * from './types'; +export * from './cases'; diff --git a/packages/utils/lib/lang.ts b/packages/common/lang.ts similarity index 100% rename from packages/utils/lib/lang.ts rename to packages/common/lang.ts diff --git a/packages/common/package.json b/packages/common/package.json new file mode 100644 index 000000000..9bf2995f0 --- /dev/null +++ b/packages/common/package.json @@ -0,0 +1,8 @@ +{ + "name": "@hydrooj/common", + "main": "./index.ts", + "version": "0.0.1", + "dependencies": { + "@hydrooj/utils": "workspace:^" + } +} diff --git a/packages/utils/lib/status.ts b/packages/common/status.ts similarity index 93% rename from packages/utils/lib/status.ts rename to packages/common/status.ts index bd82d413f..754761163 100644 --- a/packages/utils/lib/status.ts +++ b/packages/common/status.ts @@ -79,6 +79,16 @@ export const STATUS_CODES: Record = { [STATUS.STATUS_HACK_UNSUCCESSFUL]: 'fail', }; +export const NORMAL_STATUS: STATUS[] = [ + STATUS.STATUS_ACCEPTED, + STATUS.STATUS_WRONG_ANSWER, + STATUS.STATUS_TIME_LIMIT_EXCEEDED, + STATUS.STATUS_MEMORY_LIMIT_EXCEEDED, + STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED, + STATUS.STATUS_RUNTIME_ERROR, + STATUS.STATUS_COMPILE_ERROR, +]; + export function getScoreColor(score: number | string): string { if (score === null || score === undefined || !Number.isFinite(+score)) return '#000000'; return [ diff --git a/packages/utils/tests/subtask.spec.ts b/packages/common/tests/subtask.spec.ts similarity index 97% rename from packages/utils/tests/subtask.spec.ts rename to packages/common/tests/subtask.spec.ts index 3d7298668..73bbd16d0 100644 --- a/packages/utils/tests/subtask.spec.ts +++ b/packages/common/tests/subtask.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { describe, it } from 'node:test'; -import { readSubtasksFromFiles } from '../lib/common'; +import { readSubtasksFromFiles } from '../cases'; interface Info { subtask: number; diff --git a/packages/common/types.ts b/packages/common/types.ts new file mode 100644 index 000000000..094a7f666 --- /dev/null +++ b/packages/common/types.ts @@ -0,0 +1,166 @@ +export type CompilableSource = string | { + file: string; + lang: string; +}; + +export enum ProblemType { + Default = 'default', + SubmitAnswer = 'submit_answer', + Interactive = 'interactive', + Communication = 'communication', + Objective = 'objective', + Remote = 'remote_judge', +} + +export interface TestCaseConfig { + input: string; + output: string; + time?: string; + memory?: string; + score?: number; +} + +export enum SubtaskType { + min = 'min', + max = 'max', + sum = 'sum', +} + +export interface SubtaskConfig { + time?: string; + memory?: string; + score?: number; + if?: number[]; + id?: number; + type?: SubtaskType; + cases?: TestCaseConfig[]; +} + +export interface ProblemConfigFile { + type?: ProblemType; + subType?: string; + target?: string; + score?: number; + time?: string; + memory?: string; + filename?: string; + checker_type?: string; + num_processes?: number; + user_extra_files?: string[]; + judge_extra_files?: string[]; + detail?: boolean; + answers?: Record; + redirect?: string; + cases?: TestCaseConfig[]; + subtasks?: SubtaskConfig[]; + langs?: string[]; + checker?: CompilableSource; + interactor?: CompilableSource; + manager?: CompilableSource; + validator?: CompilableSource; + time_limit_rate?: Record; + memory_limit_rate?: Record; +} + +export interface FileInfo { + /** storage path */ + _id: string, + /** filename */ + name: string, + /** file size (in bytes) */ + size: number, + etag: string, + lastModified: Date, +} + +export interface JudgeMeta { + problemOwner: number; + hackRejudge?: string; + rejudge?: boolean; + // FIXME stricter types + type?: string; +} + +export interface RecordPayload { + domainId: string; + pid: number; + uid: number; + lang: string; + code: string; + score: number; + memory: number; + time: number; + judgeTexts: (string | JudgeMessage)[]; + compilerTexts: string[]; + testCases: Required[]; + rejudged: boolean; + source?: string; + /** judge uid */ + judger: number; + judgeAt: Date; + status: number; + progress?: number; + /** pretest */ + input?: string; + /** hack target rid */ + hackTarget?: string; + /** 0 if pretest&script */ + contest?: string; + + files?: Record + subtasks?: Record; +} + +export interface JudgeRequest extends Omit { + priority: number; + type: 'judge' | 'generate'; + rid: string; + config: ProblemConfigFile; + meta: JudgeMeta; + data: FileInfo[]; + source: string; + trusted: boolean; +} + +export interface TestCase { + id?: number; + subtaskId?: number; + score?: number; + time: number; + memory: number; + status: number; + message: string; +} + +export interface JudgeMessage { + message: string; + params?: string[]; + stack?: string; +} + +export interface SubtaskResult { + type: SubtaskType; + score: number; + status: number; +} + +export interface JudgeResultBody { + key: string; + domainId: string; + rid: string; + judger?: number; + progress?: number; + addProgress?: number; + case?: TestCase; + cases?: TestCase[]; + status?: number; + score?: number; + /** in miliseconds */ + time?: number; + /** in kilobytes */ + memory?: number; + message?: string | JudgeMessage; + compilerText?: string; + nop?: boolean; + subtasks?: Record; +} diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index b9c84d738..1e29563c9 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -1,11 +1,12 @@ /* eslint-disable no-await-in-loop */ import path from 'path'; import { - changeErrorType, fs, normalizeSubtasks, readSubtasksFromFiles, yaml, + convertIniConfig, LangConfig, + normalizeSubtasks, ProblemConfigFile, readSubtasksFromFiles, readYamlCases, +} from '@hydrooj/common'; +import { + changeErrorType, fs, yaml, } from '@hydrooj/utils'; -import readYamlCases, { convertIniConfig } from '@hydrooj/utils/lib/cases'; -import { LangConfig } from '@hydrooj/utils/lib/lang'; -import { ProblemConfigFile } from 'hydrooj'; import { getConfig } from './config'; import { FormatError, SystemError } from './error'; import { NextFunction, ParsedConfig } from './interface'; diff --git a/packages/hydrojudge/src/checkers.ts b/packages/hydrojudge/src/checkers.ts index 15a526755..4c57ce4eb 100644 --- a/packages/hydrojudge/src/checkers.ts +++ b/packages/hydrojudge/src/checkers.ts @@ -1,5 +1,5 @@ /* eslint-disable no-template-curly-in-string */ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; import { FormatError, SystemError } from './error'; import { CopyInFile, runQueued } from './sandbox'; import { parse } from './testlib'; diff --git a/packages/hydrojudge/src/compile.ts b/packages/hydrojudge/src/compile.ts index 142a6c02a..72512169b 100644 --- a/packages/hydrojudge/src/compile.ts +++ b/packages/hydrojudge/src/compile.ts @@ -1,5 +1,4 @@ -import { LangConfig } from '@hydrooj/utils/lib/lang'; -import { STATUS } from '@hydrooj/utils/lib/status'; +import { LangConfig, STATUS } from '@hydrooj/common'; import { CompileError } from './error'; import { Execute } from './interface'; import { diff --git a/packages/hydrojudge/src/error.ts b/packages/hydrojudge/src/error.ts index 4902914dc..2ef05a560 100644 --- a/packages/hydrojudge/src/error.ts +++ b/packages/hydrojudge/src/error.ts @@ -1,4 +1,4 @@ -import { STATUS_TEXTS } from '@hydrooj/utils/lib/status'; +import { STATUS_TEXTS } from '@hydrooj/common'; interface CompileErrorInfo { stdout?: string, diff --git a/packages/hydrojudge/src/flow.ts b/packages/hydrojudge/src/flow.ts index 95cc1177e..882434628 100644 --- a/packages/hydrojudge/src/flow.ts +++ b/packages/hydrojudge/src/flow.ts @@ -1,10 +1,10 @@ import Queue from 'p-queue'; -import { STATUS } from '@hydrooj/utils/lib/status'; -import type { JudgeResultBody } from 'hydrooj'; +import { + JudgeResultBody, NormalizedCase, NormalizedSubtask, STATUS, +} from '@hydrooj/common'; import { getConfig } from './config'; import { FormatError } from './error'; import { Context, ContextSubTask } from './judge/interface'; -import { NormalizedCase, NormalizedSubtask } from './utils'; interface Task { compile: () => Promise; diff --git a/packages/hydrojudge/src/hosts/builtin.ts b/packages/hydrojudge/src/hosts/builtin.ts index dfa86c9e6..61e510a13 100644 --- a/packages/hydrojudge/src/hosts/builtin.ts +++ b/packages/hydrojudge/src/hosts/builtin.ts @@ -36,18 +36,16 @@ const session: Session = { t._callbackAwait ||= Promise.resolve(); return (data: Partial) => { logger.debug('Next: %o', data); - data.rid = new ObjectId(t.rid); if (data.case) data.case.message ||= ''; - t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.next({ ...data, domainId: t.request.domainId })); + t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.next({ ...data, rid: t.rid, domainId: t.request.domainId })); }; }, getEnd(t: Context) { t._callbackAwait ||= Promise.resolve(); return (data: Partial) => { data.key = 'end'; - data.rid = new ObjectId(t.rid); logger.info('End: status=%d score=%d time=%dms memory=%dkb', data.status, data.score, data.time, data.memory); - t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.end({ ...data, domainId: t.request.domainId })); + t._callbackAwait = t._callbackAwait.then(() => JudgeHandler.end({ ...data, rid: t.rid, domainId: t.request.domainId })); }; }, getLang(lang: string, doThrow = true) { diff --git a/packages/hydrojudge/src/hosts/vj4.ts b/packages/hydrojudge/src/hosts/vj4.ts index b666789b6..aa4afa149 100644 --- a/packages/hydrojudge/src/hosts/vj4.ts +++ b/packages/hydrojudge/src/hosts/vj4.ts @@ -4,10 +4,8 @@ import AdmZip from 'adm-zip'; import PQueue from 'p-queue'; import superagent from 'superagent'; import WebSocket from 'ws'; +import { JudgeResultBody, parseLang, STATUS } from '@hydrooj/common'; import { fs, noop } from '@hydrooj/utils'; -import { parseLang } from '@hydrooj/utils/lib/lang'; -import { STATUS } from '@hydrooj/utils/lib/status'; -import type { JudgeResultBody } from 'hydrooj'; import { getConfig } from '../config'; import { FormatError, SystemError } from '../error'; import { Session } from '../interface'; diff --git a/packages/hydrojudge/src/info.ts b/packages/hydrojudge/src/info.ts index a73f268a3..9d415663f 100644 --- a/packages/hydrojudge/src/info.ts +++ b/packages/hydrojudge/src/info.ts @@ -1,4 +1,4 @@ -import { LangConfig } from '@hydrooj/utils/lib/lang'; +import { LangConfig } from '@hydrooj/common'; import { runQueued } from './sandbox'; export async function compilerVersions(langs: Record) { diff --git a/packages/hydrojudge/src/interface.ts b/packages/hydrojudge/src/interface.ts index 36808ba9d..12f266c7a 100644 --- a/packages/hydrojudge/src/interface.ts +++ b/packages/hydrojudge/src/interface.ts @@ -1,7 +1,6 @@ -import type { LangConfig } from '@hydrooj/utils/lib/lang'; -import { NormalizedSubtask } from '@hydrooj/utils/lib/utils'; -import type { JudgeRequest as OrigJudgeRequest, ObjectId } from 'hydrooj'; -import { JudgeResultBody, ProblemConfigFile } from 'hydrooj'; +import { + JudgeResultBody, type LangConfig, NormalizedSubtask, ProblemConfigFile, +} from '@hydrooj/common'; import { CopyInFile } from './sandbox'; import type { JudgeTask } from './task'; @@ -20,10 +19,7 @@ export interface ParsedConfig extends Omit LangConfig; diff --git a/packages/hydrojudge/src/judge/communication.ts b/packages/hydrojudge/src/judge/communication.ts index 1618bfb6a..ebb7416d9 100644 --- a/packages/hydrojudge/src/judge/communication.ts +++ b/packages/hydrojudge/src/judge/communication.ts @@ -1,8 +1,7 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { NormalizedCase, STATUS } from '@hydrooj/common'; import { runFlow } from '../flow'; import { Parameter, runPiped } from '../sandbox'; import signals from '../signals'; -import { NormalizedCase } from '../utils'; import { Context, ContextSubTask } from './interface'; function judgeCase(c: NormalizedCase) { diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index 29d9bb9ef..7441d57f6 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -1,9 +1,8 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { NormalizedCase, STATUS } from '@hydrooj/common'; import checkers from '../checkers'; import { runFlow } from '../flow'; import { del, runQueued } from '../sandbox'; import signals from '../signals'; -import { NormalizedCase } from '../utils'; import { Context, ContextSubTask } from './interface'; function judgeCase(c: NormalizedCase) { diff --git a/packages/hydrojudge/src/judge/generate.ts b/packages/hydrojudge/src/judge/generate.ts index 64413706a..92205eb19 100644 --- a/packages/hydrojudge/src/judge/generate.ts +++ b/packages/hydrojudge/src/judge/generate.ts @@ -2,7 +2,7 @@ import { tmpdir } from 'os'; import path from 'path'; import fs from 'fs-extra'; -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; import { SystemError } from '../error'; import { CopyInFile, runQueued } from '../sandbox'; import client from '../sandbox/client'; diff --git a/packages/hydrojudge/src/judge/hack.ts b/packages/hydrojudge/src/judge/hack.ts index a1210987e..46cc63945 100644 --- a/packages/hydrojudge/src/judge/hack.ts +++ b/packages/hydrojudge/src/judge/hack.ts @@ -1,10 +1,9 @@ /* eslint-disable no-sequences */ -import { fs } from '@hydrooj/utils'; -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; +import { fs, parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; import checkers from '../checkers'; import { del, runQueued } from '../sandbox'; import signals from '../signals'; -import { parseMemoryMB, parseTimeMS } from '../utils'; import { Context } from './interface'; export async function judge(ctx: Context) { @@ -13,7 +12,7 @@ export async function judge(ctx: Context) { ctx.compile(ctx.lang, ctx.code), ctx.compileLocalFile('checker', ctx.config.checker, ctx.config.checker_type), ctx.compileLocalFile('validator', ctx.config.validator), - ctx.session.fetchFile(ctx.files.hack), + ctx.session.fetchFile(null, { [ctx.files.hack]: '' }), ]); ctx.clean.push(() => fs.unlink(input)); ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 }); diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 8d6fc746e..d27996121 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -1,9 +1,8 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { NormalizedCase, STATUS } from '@hydrooj/common'; import { runFlow } from '../flow'; import { runPiped } from '../sandbox'; import signals from '../signals'; import { parse } from '../testlib'; -import { NormalizedCase } from '../utils'; import { Context, ContextSubTask } from './interface'; function judgeCase(c: NormalizedCase) { diff --git a/packages/hydrojudge/src/judge/interface.ts b/packages/hydrojudge/src/judge/interface.ts index e13ffcd96..ca9f6099d 100644 --- a/packages/hydrojudge/src/judge/interface.ts +++ b/packages/hydrojudge/src/judge/interface.ts @@ -1,5 +1,5 @@ import PQueue from 'p-queue'; -import type { NormalizedSubtask } from '@hydrooj/utils/lib/common'; +import { NormalizedSubtask } from '@hydrooj/common'; import { Execute } from '../interface'; import { JudgeTask } from '../task'; diff --git a/packages/hydrojudge/src/judge/objective.ts b/packages/hydrojudge/src/judge/objective.ts index 853096028..dbbe9ca9b 100644 --- a/packages/hydrojudge/src/judge/objective.ts +++ b/packages/hydrojudge/src/judge/objective.ts @@ -1,6 +1,6 @@ import assert from 'assert'; +import { STATUS } from '@hydrooj/common'; import { fs, yaml } from '@hydrooj/utils'; -import { STATUS } from '@hydrooj/utils/lib/status'; import { FormatError } from '../error'; import { Context } from './interface'; diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index 9aedd8e59..c058fe69f 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -1,8 +1,8 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; +import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; import { runQueued } from '../sandbox'; import signals from '../signals'; import { JudgeTask } from '../task'; -import { parseMemoryMB, parseTimeMS } from '../utils'; export const judge = async (ctx: JudgeTask) => { ctx.stat.judge = new Date(); diff --git a/packages/hydrojudge/src/judge/submit_answer.ts b/packages/hydrojudge/src/judge/submit_answer.ts index ab7febdbb..7690b7b85 100644 --- a/packages/hydrojudge/src/judge/submit_answer.ts +++ b/packages/hydrojudge/src/judge/submit_answer.ts @@ -1,9 +1,8 @@ +import { NormalizedCase, STATUS } from '@hydrooj/common'; import { fs } from '@hydrooj/utils'; -import { STATUS } from '@hydrooj/utils/lib/status'; import checkers from '../checkers'; import { runFlow } from '../flow'; import { del, runQueued } from '../sandbox'; -import { NormalizedCase } from '../utils'; import { Context } from './interface'; function judgeCase(c: NormalizedCase) { diff --git a/packages/hydrojudge/src/sandbox.ts b/packages/hydrojudge/src/sandbox.ts index 8798f78ff..0591ada45 100644 --- a/packages/hydrojudge/src/sandbox.ts +++ b/packages/hydrojudge/src/sandbox.ts @@ -2,7 +2,7 @@ import cac from 'cac'; import PQueue from 'p-queue'; import { gte } from 'semver'; import { ParseEntry } from 'shell-quote'; -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; import * as sysinfo from '@hydrooj/utils/lib/sysinfo'; import { getConfig } from './config'; import { FormatError, SystemError } from './error'; diff --git a/packages/hydrojudge/src/task.ts b/packages/hydrojudge/src/task.ts index 5c432dfa1..e99d73ae6 100644 --- a/packages/hydrojudge/src/task.ts +++ b/packages/hydrojudge/src/task.ts @@ -1,9 +1,8 @@ import { basename, join } from 'path'; +import { + CompilableSource, FileInfo, JudgeMeta, JudgeResultBody, STATUS, TestCase, +} from '@hydrooj/common'; import { findFileSync, fs } from '@hydrooj/utils'; -import { STATUS } from '@hydrooj/utils/lib/status'; -import type { - CompilableSource, FileInfo, JudgeMeta, JudgeResultBody, TestCase, -} from 'hydrooj'; import readCases from './cases'; import checkers from './checkers'; import compile from './compile'; diff --git a/packages/hydrojudge/src/testlib.ts b/packages/hydrojudge/src/testlib.ts index 16ecf89ed..6d198556e 100644 --- a/packages/hydrojudge/src/testlib.ts +++ b/packages/hydrojudge/src/testlib.ts @@ -1,4 +1,4 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; const operation = /^\s*(status|score)\((\d+)\)\s*(.*)$/m; diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index e196b167b..15ca4c26b 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -5,13 +5,12 @@ import { omit } from 'lodash'; import { ObjectId } from 'mongodb'; import PQueue from 'p-queue'; import sanitize from 'sanitize-filename'; +import { JudgeResultBody, ProblemConfigFile, TestCase } from '@hydrooj/common'; import { Context } from '../context'; import { FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError, } from '../error'; -import { - JudgeResultBody, ProblemConfigFile, RecordDoc, Task, TestCase, -} from '../interface'; +import { RecordDoc, Task } from '../interface'; import { Logger } from '../logger'; import * as builtin from '../model/builtin'; import { PERM, STATUS } from '../model/builtin'; @@ -73,11 +72,10 @@ function processPayload(body: Partial) { } export async function next(body: Partial) { - body.rid = new ObjectId(body.rid); // TODO remove this const { $set, $push, $unset, $inc, } = processPayload(body); - const rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset, $inc); + const rdoc = await record.update(body.domainId, new ObjectId(body.rid), $set, $push, $unset, $inc); if (rdoc) bus.broadcast('record/change', rdoc, $set, $push, body); return rdoc; } @@ -139,12 +137,11 @@ bus.on('record/judge', async (rdoc, updated, pdoc) => { }); export async function end(body: Partial) { - body.rid = new ObjectId(body.rid); // TODO remove this const { $set, $push } = processPayload(body); const $unset: any = { progress: '' }; $set.judgeAt = new Date(); $set.judger = body.judger ?? 1; - const rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset); + const rdoc = await record.update(body.domainId, new ObjectId(body.rid), $set, $push, $unset); if (rdoc) bus.broadcast('record/change', rdoc, null, null, body); // trigger a full update await postJudge(rdoc); return rdoc; diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 79ffe84bb..6097f1e4e 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -115,7 +115,7 @@ class SystemScriptHandler extends SystemHandler { const time = new Date().getTime() - start; judge.end({ domainId, - rid, + rid: rid.toHexString(), status: STATUS.STATUS_ACCEPTED, message: inspect(ret, false, 10, true), judger: 1, @@ -128,7 +128,7 @@ class SystemScriptHandler extends SystemHandler { logger.error(err); judge.end({ domainId, - rid, + rid: rid.toHexString(), status: STATUS.STATUS_SYSTEM_ERROR, message: `${err.message} \n${(err as any).params || []} \n${err.stack} `, judger: 1, diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index f12c220b6..2edb4803b 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -4,11 +4,16 @@ import { CredentialDeviceType } from '@simplewebauthn/types'; import type fs from 'fs'; import type { Dictionary, NumericDictionary } from 'lodash'; import type { Binary, FindCursor, ObjectId } from 'mongodb'; +import type { + FileInfo, RecordPayload, +} from '@hydrooj/common/types'; import type { Context } from './context'; import type { DocStatusType } from './model/document'; import type { ProblemDoc } from './model/problem'; import type { Handler } from './service/server'; +export * from '@hydrooj/common/types'; + type document = typeof import('./model/document'); export interface System { @@ -142,76 +147,6 @@ export interface BaseUser { } export type BaseUserDict = Record; -export interface FileInfo { - /** storage path */ - _id: string, - /** filename */ - name: string, - /** file size (in bytes) */ - size: number, - etag: string, - lastModified: Date, -} - -export interface TestCaseConfig { - input: string; - output: string; - time?: string; - memory?: string; - score?: number; -} - -export enum ProblemType { - Default = 'default', - SubmitAnswer = 'submit_answer', - Interactive = 'interactive', - Communication = 'communication', - Objective = 'objective', - Remote = 'remote_judge', -} - -export enum SubtaskType { - min = 'min', - max = 'max', - sum = 'sum', -} - -export interface SubtaskConfig { - time?: string; - memory?: string; - score?: number; - if?: number[]; - id?: number; - type?: SubtaskType; - cases?: TestCaseConfig[]; -} - -export interface ProblemConfigFile { - type?: ProblemType; - subType?: string; - target?: string; - score?: number; - time?: string; - memory?: string; - filename?: string; - checker_type?: string; - checker?: string; - interactor?: string; - manager?: string; - num_processes?: number; - user_extra_files?: string[]; - judge_extra_files?: string[]; - detail?: boolean; - answers?: Record; - redirect?: string; - cases?: TestCaseConfig[]; - subtasks?: SubtaskConfig[]; - langs?: string[]; - validator?: string; - time_limit_rate?: Record; - memory_limit_rate?: Record; -} - export interface ProblemConfig { redirect?: [string, string]; count: number; @@ -284,46 +219,11 @@ export interface ProblemStatusDoc extends StatusDocBase { star?: boolean; } -export interface TestCase { - id?: number; - subtaskId?: number; - score?: number; - time: number; - memory: number; - status: number; - message: string; -} - -export interface RecordDoc { +export type RecordDoc = { + [K in keyof RecordPayload]: K extends 'hackTarget' | 'contest' ? ObjectId : RecordPayload[K]; +} & { _id: ObjectId; - domainId: string; - pid: number; - uid: number; - lang: string; - code: string; - score: number; - memory: number; - time: number; - judgeTexts: (string | JudgeMessage)[]; - compilerTexts: string[]; - testCases: Required[]; - rejudged: boolean; - source?: string; - /** judge uid */ - judger: number; - judgeAt: Date; - status: number; - progress?: number; - /** pretest */ - input?: string; - /** hack target rid */ - hackTarget?: ObjectId; - /** 0 if pretest&script */ - contest?: ObjectId; - - files?: Record - subtasks?: Record; -} +}; export interface RecordStatDoc { _id: ObjectId; @@ -335,24 +235,6 @@ export interface RecordStatDoc { length: number; lang: string; } -export interface JudgeMeta { - problemOwner: number; - hackRejudge?: string; - rejudge?: boolean; - // FIXME stricter types - type?: string; -} - -export interface JudgeRequest extends Omit { - priority: number; - type: 'judge' | 'generate'; - rid: ObjectId; - config: ProblemConfigFile; - meta: JudgeMeta; - data: FileInfo[]; - source: string; - trusted: boolean; -} export interface ScoreboardNode { type: 'string' | 'rank' | 'user' | 'email' | 'record' | 'records' | 'problem' | 'solved' | 'time' | 'total_score'; @@ -569,39 +451,6 @@ export interface Script { validate: any, } -export interface JudgeMessage { - message: string; - params?: string[]; - stack?: string; -} - -export interface SubtaskResult { - type: SubtaskType; - score: number; - status: number; -} - -export interface JudgeResultBody { - key: string; - domainId: string; - rid: ObjectId; - judger?: number; - progress?: number; - addProgress?: number; - case?: TestCase; - cases?: TestCase[]; - status?: number; - score?: number; - /** in miliseconds */ - time?: number; - /** in kilobytes */ - memory?: number; - message?: string | JudgeMessage; - compilerText?: string; - nop?: boolean; - subtasks?: Record; -} - export interface Task { _id: ObjectId; type: string; @@ -683,6 +532,13 @@ export interface ContestBalloonDoc { sentAt?: Date; } +export interface LockDoc { + _id: ObjectId; + key: string; + lockAt: Date; + daemonId: string; +} + declare module './service/db' { interface Collections { 'blacklist': BlacklistDoc; @@ -712,6 +568,7 @@ declare module './service/db' { 'opcount': OpCountDoc; 'schedule': Schedule; 'contest.balloon': ContestBalloonDoc; + 'lock': LockDoc; } } diff --git a/packages/hydrooj/src/lib/testdataConfig.ts b/packages/hydrooj/src/lib/testdataConfig.ts index 93da3181e..4228570e3 100644 --- a/packages/hydrooj/src/lib/testdataConfig.ts +++ b/packages/hydrooj/src/lib/testdataConfig.ts @@ -1,7 +1,9 @@ import { load } from 'js-yaml'; -import readYamlCases from '@hydrooj/utils/lib/cases'; -import type { ProblemConfig, ProblemConfigFile } from '../interface'; -import { normalizeSubtasks, parseMemoryMB, parseTimeMS } from '../utils'; +import { + normalizeSubtasks, ProblemConfigFile, readYamlCases, +} from '@hydrooj/common'; +import { parseMemoryMB, parseTimeMS } from '@hydrooj/utils'; +import type { ProblemConfig } from '../interface'; export async function parseConfig(config: string | ProblemConfigFile = {}) { const cfg: ProblemConfigFile = typeof config === 'string' diff --git a/packages/hydrooj/src/model/builtin.ts b/packages/hydrooj/src/model/builtin.ts index e93112dff..a32a18522 100644 --- a/packages/hydrooj/src/model/builtin.ts +++ b/packages/hydrooj/src/model/builtin.ts @@ -2,9 +2,9 @@ import { getScoreColor, STATUS, STATUS_CODES, STATUS_SHORT_TEXTS, STATUS_TEXTS, USER_GENDER_FEMALE, USER_GENDER_ICONS, USER_GENDER_MALE, USER_GENDER_OTHER, USER_GENDER_RANGE, USER_GENDERS, -} from '@hydrooj/utils/lib/status'; +} from '@hydrooj/common'; -export * from '@hydrooj/utils/lib/status'; +export * from '@hydrooj/common/status'; export const PERM = { PERM_NONE: 0n, diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index ac0c84afc..03fc69d7e 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -8,11 +8,12 @@ import yaml from 'js-yaml'; import { pick } from 'lodash'; import { Filter, ObjectId } from 'mongodb'; import type { Readable } from 'stream'; +import { ProblemConfigFile } from '@hydrooj/common'; import { Logger, size, streamToBuffer } from '@hydrooj/utils/lib/utils'; import { Context } from '../context'; import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error'; import type { - Document, ProblemConfigFile, ProblemDict, ProblemStatusDoc, User, + Document, ProblemDict, ProblemStatusDoc, User, } from '../interface'; import { parseConfig } from '../lib/testdataConfig'; import * as bus from '../service/bus'; diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 26b6715de..c5e987890 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -5,11 +5,10 @@ import { Filter, FindOptions, MatchKeysAndValues, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, } from 'mongodb'; +import { ProblemConfigFile } from '@hydrooj/common'; import { Context } from '../context'; import { ProblemNotFoundError } from '../error'; -import { - JudgeMeta, ProblemConfigFile, RecordDoc, -} from '../interface'; +import { JudgeMeta, RecordDoc } from '../interface'; import db from '../service/db'; import { MaybeArray, NumberKeys } from '../typeutils'; import { ArgMethod, buildProjection, Time } from '../utils'; diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index bc55fa952..171db78a8 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -4,8 +4,8 @@ import * as cordis from 'cordis'; import yaml from 'js-yaml'; import { Dictionary } from 'lodash'; import moment from 'moment-timezone'; -import { LangConfig, parseLang } from '@hydrooj/utils/lib/lang'; -import { retry } from '@hydrooj/utils/lib/utils'; +import { LangConfig, parseLang } from '@hydrooj/common'; +import { retry } from '@hydrooj/utils'; import { Context, Service } from '../context'; import { Setting as _Setting } from '../interface'; import { Logger } from '../logger'; diff --git a/packages/ui-default/backendlib/template.ts b/packages/ui-default/backendlib/template.ts index 51cd17e26..0d696a624 100644 --- a/packages/ui-default/backendlib/template.ts +++ b/packages/ui-default/backendlib/template.ts @@ -1,4 +1,4 @@ -import * as status from '@hydrooj/utils/lib/status'; +import * as status from '@hydrooj/common/status'; import { findFileSync } from '@hydrooj/utils/lib/utils'; import { avatar, Context, fs, PERM, PRIV, STATUS, yaml, diff --git a/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx b/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx index 87b3a13e2..f4a0d76dd 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigTree.tsx @@ -1,7 +1,7 @@ import { Button, Classes, Icon, Popover, TreeNode, } from '@blueprintjs/core'; -import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common'; +import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/common'; import { TestCaseConfig } from 'hydrooj'; import { isEqual } from 'lodash'; import React from 'react'; diff --git a/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx b/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx index 3c68310a5..26347c2bb 100644 --- a/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx +++ b/packages/ui-default/components/problemconfig/tree/AddTestcase.tsx @@ -1,7 +1,7 @@ import { Button, Classes, Dialog, DialogBody, DialogFooter, Icon, } from '@blueprintjs/core'; -import { readSubtasksFromFiles } from '@hydrooj/utils/lib/common'; +import { readSubtasksFromFiles } from '@hydrooj/common'; import React, { useEffect, useRef } from 'react'; import { useSelector, useStore } from 'react-redux'; import { i18n } from 'vj/utils'; diff --git a/packages/ui-default/components/scratchpad/reducers/pretest.ts b/packages/ui-default/components/scratchpad/reducers/pretest.ts index 7b6eb7f18..2ae4354e2 100644 --- a/packages/ui-default/components/scratchpad/reducers/pretest.ts +++ b/packages/ui-default/components/scratchpad/reducers/pretest.ts @@ -1,4 +1,4 @@ -import { STATUS_TEXTS } from '@hydrooj/utils/lib/status'; +import { STATUS_TEXTS } from '@hydrooj/common'; export default function reducer(state = { input: '', diff --git a/packages/ui-default/pages/problem_config.page.tsx b/packages/ui-default/pages/problem_config.page.tsx index 3f96b9b96..da379e97e 100644 --- a/packages/ui-default/pages/problem_config.page.tsx +++ b/packages/ui-default/pages/problem_config.page.tsx @@ -1,5 +1,4 @@ -import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common'; -import type { SubtaskType } from 'hydrooj/src/interface'; +import { normalizeSubtasks, readSubtasksFromFiles, SubtaskType } from '@hydrooj/common'; import $ from 'jquery'; import yaml from 'js-yaml'; import React from 'react'; diff --git a/packages/ui-default/pages/problem_files.page.tsx b/packages/ui-default/pages/problem_files.page.tsx index 3d1532ce6..00fc90d54 100644 --- a/packages/ui-default/pages/problem_files.page.tsx +++ b/packages/ui-default/pages/problem_files.page.tsx @@ -1,4 +1,4 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; import $ from 'jquery'; import { map } from 'lodash'; import React from 'react'; diff --git a/packages/ui-default/pages/problem_statistics.page.ts b/packages/ui-default/pages/problem_statistics.page.ts index 2f6a10f72..c06b3e80f 100644 --- a/packages/ui-default/pages/problem_statistics.page.ts +++ b/packages/ui-default/pages/problem_statistics.page.ts @@ -1,4 +1,4 @@ -import { getScoreColor } from '@hydrooj/utils/lib/status'; +import { getScoreColor } from '@hydrooj/common'; import $ from 'jquery'; import { NamedPage } from 'vj/misc/Page'; import { getAvailableLangs, tpl } from 'vj/utils'; diff --git a/packages/utils/lib/cases.ts b/packages/utils/lib/cases.ts deleted file mode 100644 index 880977083..000000000 --- a/packages/utils/lib/cases.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { findFileSync, parseMemoryMB, parseTimeMS } from './utils'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export default async function readYamlCases(cfg: Record = {}, checkFile = (s: string, errMsg: string) => s) { - const config: any = { - checker_type: cfg.checker_type || 'default', - judge_extra_files: [], - user_extra_files: [], - }; - if (cfg.type === 'objective') { - if (cfg.checker || cfg.interactor || cfg.validator) { - throw new Error('You cannot use checker, interactor or validator for objective questions'); - } - } else { - if (cfg.checker) { - if (!cfg.checker.includes('.')) { - config.checker = findFileSync(`@hydrooj/hydrojudge/vendor/testlib/checkers/${cfg.checker}.cpp`, false); - } - config.checker ||= checkFile(cfg.checker, 'Cannot find checker {0}.'); - } - if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.'); - if (cfg.manager) config.manager = checkFile(cfg.manager, 'Cannot find Manager {0}.'); - if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.'); - for (const n of ['judge', 'user']) { - const conf = cfg[`${n}_extra_files`]; - if (!conf) continue; - if (conf instanceof Array) { - config[`${n}_extra_files`] = conf.map((file) => checkFile(file, `Cannot find ${n} extra file {0}.`)); - } else throw new Error(`Invalid ${n}_extra_files config.`); - } - } - if (cfg.cases?.length) { - config.subtasks = [{ - cases: cfg.cases, - type: 'sum', - }]; - } - if (cfg.time) config.time = parseTimeMS(cfg.time); - if (cfg.memory) config.memory = parseMemoryMB(cfg.memory); - return Object.assign(cfg, config); -} - -export function convertIniConfig(ini: string) { - const f = ini.split('\n'); - const count = parseInt(f[0], 10); - const res = { subtasks: [] }; - for (let i = 1; i <= count; i++) { - if (!f[i]?.trim()) throw new Error('Testdata count incorrect.'); - const [input, output, time, score, memory] = f[i].split('|'); - const cur = { - cases: [{ input, output }], - score: parseInt(score, 10), - time: `${time}s`, - memory: '256m', - }; - if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; - res.subtasks.push(cur); - } - return res; -} diff --git a/packages/utils/lib/common.ts b/packages/utils/lib/common.ts index 9d7d68ba8..bdf2186cd 100644 --- a/packages/utils/lib/common.ts +++ b/packages/utils/lib/common.ts @@ -236,159 +236,3 @@ export function sortFiles(files: Record[] | string[], key = '_id') }); return result.map((x) => (isString ? x.name : (delete x._weights && x))); } - -interface MatchRule { - regex: RegExp; - output: (a: RegExpExecArray) => string[]; - id: (a: RegExpExecArray) => number; - subtask: (a: RegExpExecArray) => number; - preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum'; -} - -const SubtaskMatcher: MatchRule[] = [ - { - regex: /^(([A-Za-z0-9._-]*?)(?:(\d*)[-_])?(\d+))\.(in|IN|txt|TXT|in\.txt|IN\.TXT)$/, - output: (a) => ['out', 'ans'] - .flatMap((i) => [i, i.toUpperCase(), `${i}.txt`, `${i.toUpperCase()}.TXT`]) - .flatMap((i) => [`${a[1]}.${i}`, `${a[1]}.${i}`.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]) - .concat(a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null), - id: (a) => +a[4], - subtask: (a) => +(a[3] || 1), - preferredScorerType: (a) => (a[3] ? 'min' : 'sum'), - }, - { - regex: /^([^\d]*)\.(in|IN)(\d+)$/, - output: (a) => [ - `${a[1]}.${a[2] === 'in' ? 'ou' : 'OU'}${a[3]}`, - `${a[1]}.${a[2] === 'in' ? 'out' : 'OUT'}${a[3]}`, - ].flatMap((i) => [i, i.replace(/input/g, 'output').replace(/INPUT/g, 'OUTPUT')]), - id: (a) => +a[2], - subtask: () => 1, - preferredScorerType: () => 'sum', - }, - { - regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.(in|IN)$/, - output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}${a[2]}${a[3]}${a[4]}.${i}`), - id: (a) => +a[4], - subtask: (a) => +a[2], - preferredScorerType: () => 'min', - }, - { - regex: /^(([0-9]+)[-_](?:.*))\.(in|IN)$/, - output: (a) => ['out', 'ans', 'OUT', 'ANS'].flatMap((i) => `${a[1]}.${i}`), - id: (a) => +a[2], - subtask: () => 1, - preferredScorerType: () => 'sum', - }, -]; - -function* getScore(totalScore: number, count: number) { - const base = Math.floor(totalScore / count); - const extra = count - (totalScore % count); - for (let i = 0; i < count; i++) { - if (i >= extra) yield base + 1; - else yield base; - } -} - -interface ParsedCase { - id?: number; - time?: number | string; - memory?: number | string; - score?: number; - input?: string; - output?: string; -} -interface ParsedSubtask { - cases: ParsedCase[]; - type: 'min' | 'max' | 'sum'; - time?: number | string; - memory?: number | string; - score?: number; - id?: number; - if?: number[]; -} - -export function readSubtasksFromFiles(files: string[], config) { - const subtask: Record = {}; - for (const s of config.subtasks || []) if (s.id) subtask[s.id] = s; - for (const file of files) { - let match = false; - for (const rule of SubtaskMatcher) { - const data = rule.regex.exec(file); - if (!data) continue; - const sid = rule.subtask(data); - const c = { input: file, output: '', id: rule.id(data) }; - const type = rule.preferredScorerType(data); - const outputs = (config.noOutputFile ? ['/dev/null'] : rule.output(data)).filter((i) => i); - for (const output of outputs) { - if (output === file) continue; - if (output === '/dev/null' || files.includes(output)) { - match = true; - c.output = output; - if (!subtask[sid]) { - subtask[sid] = { - time: config.time, - memory: config.memory, - type, - cases: [c], - id: sid, - }; - } else if (!subtask[sid].cases) subtask[sid].cases = [c]; - else subtask[sid].cases.push(c); - break; - } - } - if (match) break; - } - } - return Object.values(subtask); -} - -export interface NormalizedCase extends Required { - time: number; - memory: number; -} -export interface NormalizedSubtask extends Required { - cases: NormalizedCase[]; - time: number; - memory: number; -} - -export function normalizeSubtasks( - subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string, - time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false, - timeRate = 1, memoryRate = 1, -): NormalizedSubtask[] { - subtasks.sort((a, b) => (a.id - b.id)); - const subtaskScore = getScore( - Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0), - subtasks.filter((i) => !i.score).length, - ); - return subtasks.map((s, id) => { - s.cases.sort((a, b) => (a.id - b.id)); - const score = s.score || subtaskScore.next().value as number; - const caseScore = getScore( - Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0), - s.cases.filter((i) => !i.score).length, - ); - return { - id: id + 1, - type: 'min', - if: [], - ...s, - score, - time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate, - memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate, - cases: s.cases.map((c, index) => ({ - id: index + 1, - ...c, - score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score), - time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate, - memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate, - input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', - output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', - })) as NormalizedCase[], - }; - }); -} diff --git a/packages/vjudge/src/index.ts b/packages/vjudge/src/index.ts index 3417fc5a0..77013a0ed 100644 --- a/packages/vjudge/src/index.ts +++ b/packages/vjudge/src/index.ts @@ -1,9 +1,10 @@ /* eslint-disable no-await-in-loop */ import os from 'os'; -import { LangConfig } from '@hydrooj/utils/lib/lang'; +import { LangConfig, STATUS } from '@hydrooj/common'; import { - Context, db, DomainModel, JudgeHandler, Logger, ProblemModel, RecordModel, Service, SettingModel, - sleep, STATUS, SystemModel, TaskModel, Time, yaml, + Context, db, DomainModel, JudgeHandler, Logger, + ProblemModel, RecordModel, Service, SettingModel, + sleep, SystemModel, TaskModel, Time, yaml, } from 'hydrooj'; import { BasicProvider, IBasicProvider, RemoteAccount } from './interface'; import providers from './providers/index'; diff --git a/packages/vjudge/src/interface.ts b/packages/vjudge/src/interface.ts index 297b62508..23b825f70 100644 --- a/packages/vjudge/src/interface.ts +++ b/packages/vjudge/src/interface.ts @@ -1,5 +1,4 @@ -import { LangConfig } from '@hydrooj/utils/lib/lang'; -import { JudgeResultBody } from 'hydrooj'; +import { JudgeResultBody, LangConfig } from '@hydrooj/common'; export interface RemoteAccount { _id: string; diff --git a/packages/vjudge/src/verdict.ts b/packages/vjudge/src/verdict.ts index 39711bf66..15c4cab68 100644 --- a/packages/vjudge/src/verdict.ts +++ b/packages/vjudge/src/verdict.ts @@ -1,4 +1,4 @@ -import { STATUS } from '@hydrooj/utils/lib/status'; +import { STATUS } from '@hydrooj/common'; export function normalize(key: string) { return key.toUpperCase().replace(/ /g, '_');