update interface: ProblemConfig

This commit is contained in:
undefined committed 2020-07-24 17:22:59 +08:00
1 parent 92335d8529
commit 57881792f3
15 files changed
+226 -145

No files matched your search

+1
View File
@@ -14,6 +14,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
camelcase: 'off',
'@typescript-eslint/no-unused-vars': 'error',
'global-require': 'off',
'guard-for-in': 'off',
+4 -8
View File
@@ -304,14 +304,10 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
@param('code', Types.String)
async post(domainId: string, tid: ObjectID, lang: string, code: string) {
await this.limitRate('add_record', 60, 100);
const rid = await record.add(domainId, {
pid: this.pdoc.docId,
uid: this.user._id,
tid: this.tdoc.docId,
ttype: document.TYPE_CONTEST,
lang,
code,
}, true);
const rid = await record.add(domainId, this.pdoc.docId, this.user._id, lang, code, true, {
type: document.TYPE_CONTEST,
tid,
});
const [rdoc] = await Promise.all([
record.get(domainId, rid),
contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId),
+4 -3
View File
@@ -181,9 +181,10 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
if (!tsdoc.attend) throw new HomeworkNotAttendedError(tid);
if (!contest.isOngoing(this.tdoc)) throw new HomeworkNotLiveError(tid);
if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid);
const rid = await record.add(domainId, {
pid, lang, code, uid: this.user._id, tid, hidden: true, ttype: document.TYPE_HOMEWORK,
}, true);
const rid = await record.add(domainId, pid, this.user._id, lang, code, true, {
type: document.TYPE_HOMEWORK,
tid,
});
const [rdoc] = await Promise.all([
record.get(domainId, rid),
contest.updateStatus(domainId, tid, this.user._id,
+3 -40
View File
@@ -16,11 +16,11 @@ import {
async function _postJudge(rdoc: Rdoc) {
const accept = rdoc.status === builtin.STATUS.STATUS_ACCEPTED;
const tasks = [];
if (rdoc.tid) {
if (rdoc.contest) {
tasks.push(
contest.updateStatus(
rdoc.domainId, rdoc.tid, rdoc.uid,
rdoc._id, rdoc.pid, accept, rdoc.score, rdoc.ttype,
rdoc.domainId, rdoc.contest.tid, rdoc.uid,
rdoc._id, rdoc.pid, accept, rdoc.score, rdoc.contest.type,
),
);
}
@@ -93,42 +93,6 @@ export async function end(body: JudgeResultBody) {
bus.publish('record_change', { rdoc }); // trigger a full update
}
class PretestHandler extends Handler {
@param('pid', Types.UnsignedInt, true)
@param('code', Types.String)
@param('lang', Types.String)
@param('time', Types.String, true)
@param('memory', Types.String, true)
@param('input', Types.String, true)
async post(
domainId: string, pid = 0,
code: string, lang: string,
time = '1s', memory = '256m', input = '',
) {
if (pid) {
const pdoc = await problem.get(domainId, pid);
if (pdoc.config) {
const config: any = yaml.safeLoad(pdoc.config);
if (config.time) time = config.time;
if (config.memory) memory = config.memory;
}
}
const rid = await record.add(domainId, {
pid,
uid: this.user._id,
type: 'run',
time: parseTimeMS(time),
memory: parseMemoryMB(memory),
input,
lang,
code,
hidden: true,
}, true);
this.response.body = { rid };
this.response.redirect = this.url('record_detail', { rid });
}
}
class JudgeHandler extends Handler {
async get({ check = false }) {
if (check) return;
@@ -174,7 +138,6 @@ class JudgeConnectionHandler extends ConnectionHandler {
}
export async function apply() {
Route('pretest', '/pretest', PretestHandler);
Route('judge', '/judge', JudgeHandler, builtin.PRIV.PRIV_JUDGE);
Connection('judge_conn', '/judge/conn', JudgeConnectionHandler, builtin.PRIV.PRIV_JUDGE);
}
+3 -10
View File
@@ -89,18 +89,11 @@ class SystemScriptHandler extends SystemHandler {
@param('id', Types.String)
@param('args', Types.String, true)
async post(domainId: string, id: string, args = '{}') {
async post(domainId: string, id: string, raw = '{}') {
if (!global.Hydro.script[id]) throw new ValidationError('id');
args = JSON.parse(args);
const args = JSON.parse(raw);
validate(global.Hydro.script[id].validate, args);
const rid = await record.add(domainId, {
pid: -1,
uid: this.user._id,
lang: '-',
code: id,
status: STATUS.STATUS_JUDGING,
hidden: true,
}, false);
const rid = await record.add(domainId, -1, this.user._id, '-', id, false, { input: 'raw' });
judge.next({ domainId, rid, message: `Running script: ${id}` });
async function report(data) {
judge.next({ domainId, rid, ...data });
+22 -15
View File
@@ -24,7 +24,9 @@ import {
ValidationError,
PermissionError,
} from '../error';
import { Pdoc, User, Rdoc } from '../interface';
import {
Pdoc, User, Rdoc, ProblemConfig,
} from '../interface';
const parseCategory = (value: string) => {
if (!value) return [];
@@ -179,12 +181,9 @@ class ProblemDetailHandler extends ProblemHandler {
if (this.pdoc.hidden && this.pdoc.owner !== this.user._id) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
const config: any = this.pdoc.config ? yaml.safeLoad(this.pdoc.config) : {};
this.udoc = await user.getById(domainId, this.pdoc.owner);
this.response.body = {
pdoc: this.pdoc,
time: config.time,
memory: config.memory,
udoc: this.udoc,
title: this.pdoc.title,
path: [
@@ -255,14 +254,12 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
@param('lang', Types.String)
@param('code', Types.String)
async post(domainId: string, lang: string, code: string) {
const rid = await record.add(domainId, {
uid: this.user._id, lang, code, pid: this.pdoc.docId,
}, true);
const rid = await record.add(domainId, this.pdoc.docId, this.user._id, lang, code, true);
const [rdoc] = await Promise.all([
record.get(domainId, rid),
domain.incUserInDomain(domainId, this.user._id, 'nSubmit'),
]);
bus.publish('record_change', rdoc);
bus.publish('record_change', { rdoc });
this.response.body = { rid };
this.response.redirect = this.url('record_detail', { rid });
}
@@ -274,11 +271,18 @@ class ProblemPretestHandler extends ProblemDetailHandler {
@param('input', Types.String, true)
async post(domainId: string, lang: string, code: string, input: string = '') {
this.limitRate('add_record', 3600, 100);
const rid = await record.add(domainId, {
uid: this.user._id, lang, code, pid: this.pdoc.docId, input,
}, true);
// TODO parseConfig
const rid = await record.add(
domainId, this.pdoc.docId, this.user._id,
lang, code, true,
{
input,
time: 1000,
memory: 256,
},
);
const rdoc = await record.get(domainId, rid);
bus.publish('record_change', rdoc);
bus.publish('record_change', { rdoc });
this.response.body = { rid };
}
}
@@ -311,7 +315,8 @@ class ProblemPretestConnectionHandler extends ConnectionHandler {
rdoc.testCases = rdoc.testCases.map((c) => ({
status: c.status,
}));
if (rdoc.tid) return;
// TODO handle update
if (rdoc.contest) return;
this.send({ rdoc });
}
@@ -354,9 +359,11 @@ class ProblemSettingsHandler extends ProblemManageHandler {
@param('pid', Types.String, null, parsePid)
@param('yaml', Types.String)
async postConfig(domainId: string, pid: string | number, yaml: string) {
async postConfig(domainId: string, pid: string | number, cfg: string) {
const pdoc = await problem.get(domainId, pid);
await problem.edit(domainId, pdoc.docId, { config: yaml });
// TODO validate
const config = yaml.safeLoad(cfg);
await problem.edit(domainId, pdoc.docId, { config });
this.back();
}
+5 -8
View File
@@ -63,14 +63,13 @@ class RecordDetailHandler extends RecordHandler {
async get(domainId: string, rid: ObjectID) {
this.response.template = 'record_detail.html';
const rdoc = await record.get(domainId, rid);
if (rdoc.tid) {
const tdoc = await contest.get(domainId, rdoc.tid, rdoc.ttype);
if (rdoc.contest) {
const tdoc = await contest.get(domainId, rdoc.contest.tid, rdoc.contest.type);
if (!this.canShowRecord(tdoc, true)) throw new PermissionError(rid);
}
if (rdoc.uid !== this.user.uid && !this.user.hasPerm(PERM.PERM_READ_RECORD_CODE)) {
rdoc.code = null;
}
if (rdoc.type !== 'run') rdoc.stdout = rdoc.stderr = '';
const [pdoc, udoc] = await Promise.all([
problem.get(domainId, rdoc.pid, null),
user.getById(domainId, rdoc.uid),
@@ -83,8 +82,6 @@ class RecordDetailHandler extends RecordHandler {
udoc,
rdoc,
pdoc,
// TODO protect
showStatus: true,
};
}
}
@@ -134,7 +131,7 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
async onRecordChange(data) {
const rdoc: Rdoc = data.value.rdoc;
if (rdoc.tid && rdoc.tid.toString() !== this.tid) return;
if (rdoc.contest && rdoc.contest.tid.toString() !== this.tid) return;
// eslint-disable-next-line prefer-const
let [udoc, pdoc] = await Promise.all([
user.getById(this.domainId, rdoc.uid),
@@ -149,8 +146,8 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti
@param('rid', Types.ObjectID)
async prepare(domainId: string, rid: ObjectID) {
const rdoc = await record.get(domainId, rid);
if (rdoc.tid) {
const tdoc = await contest.get(domainId, rdoc.tid, -1);
if (rdoc.contest) {
const tdoc = await contest.get(domainId, rdoc.contest.tid, -1);
if (!this.canShowRecord(tdoc)) {
this.close();
return;
+75 -8
View File
@@ -62,6 +62,66 @@ export interface ProblemDataSource {
// ObjectID for built-in files, ProblemDataSource for other source (RemoteJudge, etc.)
export type ProblemData = ObjectID | ProblemDataSource;
export interface TestCaseConfig {
input: string,
output: string,
time?: number,
memory?: number,
}
export enum LocalProblemType {
Default = 'default',
SubmitAnswer = 'submit_answer',
Interactive = 'interactive',
}
export enum RemoteProblemType {
RemoteJudge = 'remotejudge',
}
export enum RemoteServerType {
vj4 = 'vj4',
syzoj = 'syzoj',
}
export enum SubtaskType {
min = 'min',
max = 'max',
sum = 'sum',
}
export interface SubtaskConfig {
time?: number,
memory?: number,
score?: number,
type?: SubtaskType,
cases?: TestCaseConfig[],
}
export interface LocalProblemConfig {
type?: LocalProblemType,
score?: number,
time?: number,
memory?: number,
checker_type?: string,
checker?: string,
interactor?: string,
user_extra_files?: string[],
judge_extra_files?: string[],
cases?: TestCaseConfig[],
subtasks?: SubtaskConfig[],
}
export interface RemoteProblemConfig {
type?: RemoteProblemType,
server_type?: RemoteServerType,
url?: string,
// TODO handle username&password storage
pid?: string,
}
export type ProblemConfig = LocalProblemConfig | RemoteProblemConfig;
export interface Pdoc {
_id: ObjectID,
domainId: string,
@@ -77,7 +137,7 @@ export interface Pdoc {
category: string[],
data?: ProblemData,
hidden: boolean,
config: string,
config: ProblemConfig,
acMsg?: string,
}
@@ -90,6 +150,17 @@ export interface TestCase {
message: string,
}
export interface PretestConfig {
time?: number,
memory?: number,
input: string,
}
export interface ContestInfo {
type: number,
tid: ObjectID,
}
export interface Rdoc {
_id: ObjectID,
domainId: string,
@@ -107,13 +178,9 @@ export interface Rdoc {
judger: string,
judgeAt: Date,
status: number,
type: string,
hidden: boolean,
input?: string,
stdout?: string,
stderr?: string,
tid?: ObjectID,
ttype?: number,
config?: PretestConfig,
contest?: ContestInfo,
}
export interface ScoreboardNode {
@@ -279,7 +346,7 @@ declare global {
paginate: typeof import('./lib/paginate').default,
rank: typeof import('./lib/rank').default,
rating: typeof import('./lib/rating').default,
readConfig: typeof import('./lib/readConfig').default,
testdataConfig: typeof import('./lib/testdataConfig'),
sha1: typeof import('./lib/sha1').default,
sysinfo: typeof import('./lib/sysinfo'),
template: typeof import('./lib/template'),
-21
View File
@@ -1,21 +0,0 @@
import Zip from 'adm-zip';
async function readConfig(filePath: string) {
const data = new Zip(filePath);
const entries = data.getEntries();
for (const entry of entries) {
if (entry.name.toLowerCase() === 'config.yaml') {
return entry.getData().toString();
}
if (entry.name.toLowerCase() === 'config.ini') {
const ini = entry.getData().toString();
const conv = global.Hydro.lib['testdata.convert.ini'];
return conv(ini);
}
}
return '';
}
global.Hydro.lib.readConfig = readConfig;
export default readConfig;
+60
View File
@@ -0,0 +1,60 @@
import Zip from 'adm-zip';
import { safeLoad } from 'js-yaml';
import { LocalProblemConfig } from '../interface';
export async function readConfig(filePath: string) {
const data = new Zip(filePath);
const entries = data.getEntries();
for (const entry of entries) {
if (entry.name.toLowerCase() === 'config.yaml') {
return entry.getData().toString();
}
if (entry.name.toLowerCase() === 'config.ini') {
const ini = entry.getData().toString();
const conv = global.Hydro.lib['testdata.convert.ini'];
return conv(ini);
}
}
return '';
}
interface ParseResult {
count: number,
memoryMax: number,
memoryMin: number,
timeMax: number,
timeMin: number,
}
export async function parseConfig(config: string | LocalProblemConfig) {
const result: ParseResult = {
count: 0,
memoryMin: Number.MAX_SAFE_INTEGER,
memoryMax: 0,
timeMin: Number.MAX_SAFE_INTEGER,
timeMax: 0,
};
let cfg: LocalProblemConfig = {};
if (typeof config === 'string') {
// TODO should validate here?
cfg = safeLoad(config) as LocalProblemConfig;
} else cfg = config;
if (cfg.cases) {
for (const c of cfg.cases) {
result.memoryMax = Math.max(result.memoryMax, c.memory);
result.memoryMin = Math.min(result.memoryMin, c.memory);
result.timeMax = Math.max(result.timeMax, c.time);
result.timeMin = Math.min(result.timeMin, c.time);
result.count++;
}
} else if (cfg.subtasks) {
for (const subtask of cfg.subtasks) {
result.memoryMax = Math.max(result.memoryMax, subtask.memory);
result.memoryMin = Math.min(result.memoryMin, subtask.memory);
result.timeMax = Math.max(result.timeMax, subtask.time);
result.timeMin = Math.min(result.timeMin, subtask.time);
}
}
}
global.Hydro.lib.testdataConfig = { readConfig, parseConfig };
+6 -1
View File
@@ -10,6 +10,8 @@ import fs from 'fs-extra';
import { argv } from 'yargs';
import AdmZip from 'adm-zip';
export * from './interface';
global.Hydro = {
stat: { reqCount: 0 },
handler: {},
@@ -65,7 +67,10 @@ async function entry(config: EntryConfig) {
const sargv = [`--entry=${config.entry}`];
const p = await fork(sargv);
await new Promise((resolve, reject) => {
p.on('exit', resolve);
p.on('exit', (code, signal) => {
if (code === 0) resolve();
else reject(signal);
});
p.on('error', (err: Error) => {
p.kill();
reject(err);
+6 -14
View File
@@ -1,17 +1,15 @@
import { ObjectID } from 'mongodb';
import * as user from './user';
import * as problem from './problem';
import * as document from './document';
import { PERM } from './builtin';
import {
ValidationError, ContestNotFoundError, ContestAlreadyAttendedError,
ContestNotAttendedError, ContestScoreboardHiddenError,
} from '../error';
import * as document from './document';
import { PERM } from './builtin';
import {
Tdoc, ContestRule, ContestRules,
Udict, ScoreboardNode,
ContestRule, ContestRules, ScoreboardNode, Tdoc, Udict,
} from '../interface';
import * as validator from '../lib/validator';
import * as misc from '../lib/misc';
import ranked from '../lib/rank';
@@ -21,7 +19,7 @@ const acm: ContestRule = {
statusSort: { accept: -1, time: 1 },
showScoreboard: () => true,
showRecord: (tdoc, now) => now > tdoc.endAt,
stat: (tdoc, journal: any[]) => {
stat: (tdoc, journal) => {
const naccept = {};
const effective = {};
const detail = [];
@@ -156,12 +154,8 @@ const oi: ContestRule = {
}
return { score, detail };
},
showScoreboard(tdoc, now) {
return now > tdoc.endAt;
},
showRecord(tdoc, now) {
return now > tdoc.endAt;
},
showScoreboard: (tdoc, now) => now > tdoc.endAt,
showRecord: (tdoc, now) => now > tdoc.endAt,
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
const columns: ScoreboardNode[] = [
{ type: 'rank', value: _('Rank') },
@@ -364,8 +358,6 @@ export function add(
rule: string, beginAt = new Date(), endAt = new Date(), pids = [],
rated = false, data = {}, type = document.TYPE_CONTEST,
) {
validator.checkTitle(title);
validator.checkContent(content);
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
Object.assign(data, {
+3 -3
View File
@@ -5,7 +5,7 @@ import * as document from './document';
import * as domain from './domain';
import { Pdoc, Pdict, ProblemDataSource } from '../interface';
import { ProblemNotFoundError } from '../error';
import readConfig from '../lib/readConfig';
import * as testdataConfig from '../lib/testdataConfig';
export const pdocHidden: Pdoc = {
_id: new ObjectID(),
@@ -22,7 +22,7 @@ export const pdocHidden: Pdoc = {
category: [],
data: null,
hidden: true,
config: '',
config: {},
acMsg: '',
};
@@ -151,7 +151,7 @@ export function setStar(domainId: string, pid: number, uid: number, star: boolea
export async function setTestdata(domainId: string, _id: number, filePath: string) {
const pdoc = await get(domainId, _id);
const config = await readConfig(filePath);
const config = await testdataConfig.readConfig(filePath);
const id = await file.add(filePath, 'data.zip', 1);
if (pdoc.data instanceof ObjectID) file.del(pdoc.data);
return await edit(domainId, _id, { data: id, config });
+33 -13
View File
@@ -4,7 +4,9 @@ import { ObjectID } from 'mongodb';
import { STATUS } from './builtin';
import * as task from './task';
import * as problem from './problem';
import { Rdoc, TestCase } from '../interface';
import {
Rdoc, TestCase, PretestConfig, ContestInfo,
} from '../interface';
import { RecordNotFoundError } from '../error';
import * as db from '../service/db';
@@ -46,28 +48,50 @@ export interface JudgeTask {
type?: string,
}
export async function add(domainId: string, data: RdocBase, addTask: boolean): Promise<ObjectID> {
_.defaults(data, {
export async function add(
domainId: string, pid: number, uid: number,
lang: string, code: string, addTask: boolean, contest?: ContestInfo,
): Promise<ObjectID>
export async function add(
domainId: string, pid: number, uid: number,
lang: string, code: string, addTask: boolean, config: PretestConfig,
): Promise<ObjectID>
export async function add(
domainId: string, pid: number, uid: number,
lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | PretestConfig,
) {
const data: Rdoc = {
status: STATUS.STATUS_WAITING,
type: 'judge',
_id: new ObjectID(),
uid,
code,
lang,
pid,
domainId,
score: 0,
time: 0,
memory: 0,
hidden: false,
hidden: !!contestOrConfig,
judgeTexts: [],
compilerTexts: [],
testCases: [],
judger: null,
judgeAt: null,
});
rejudged: false,
};
if ((contestOrConfig as ContestInfo).type) {
// is contest
data.contest = contestOrConfig as ContestInfo;
} else {
data.config = contestOrConfig as PretestConfig;
}
const [pdoc, res] = await Promise.all([
problem.get(domainId, data.pid, null),
problem.get(domainId, pid, null),
coll.insertOne(data),
]);
if (addTask) {
const t: any = {
type: data.type || 'judge',
type: 'judge',
rid: res.insertedId,
domainId,
pid: data.pid,
@@ -78,11 +102,7 @@ export async function add(domainId: string, data: RdocBase, addTask: boolean): P
t.data = pdoc.data;
t.config = pdoc.config;
} else {
t.config = yaml.safeDump({
time: data.time,
memory: data.memory,
input: data.input,
});
t.config = yaml.safeDump(contestOrConfig);
}
await task.add(t);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.9.13",
"version": "2.10.0",
"main": "dist/loader.js",
"bin": "bin/hydrooj.js",
"repository": "https://github.com/hydro-dev/Hydro.git",