judge: support generate testdata (#695)

Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
undefinedandpanda authored and GitHub committed 2023-12-14 17:03:42 +08:00
1 parent b83b2e792e
commit 55b4e1aaf0
27 files changed
+454 -68

No files matched your search

+45 -2
View File
@@ -1,20 +1,27 @@
import assert from 'assert';
import fs from 'fs-extra';
import yaml from 'js-yaml';
import { omit } from 'lodash';
import { ObjectId } from 'mongodb';
import sanitize from 'sanitize-filename';
import {
FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError,
} from '../error';
import {
JudgeResultBody, ProblemConfigFile, RecordDoc, Task, TestCase,
} from '../interface';
import { Logger } from '../logger';
import * as builtin from '../model/builtin';
import { STATUS } from '../model/builtin';
import { PERM, STATUS } from '../model/builtin';
import * as contest from '../model/contest';
import domain from '../model/domain';
import problem from '../model/problem';
import record from '../model/record';
import * as setting from '../model/setting';
import storage from '../model/storage';
import * as system from '../model/system';
import task from '../model/task';
import user from '../model/user';
import * as bus from '../service/bus';
import { updateJudge } from '../service/monitor';
import {
@@ -189,11 +196,46 @@ export class SubmissionDataDownloadHandler extends Handler {
}
}
export async function processJudgeFileCallback(rid: ObjectId, filename: string, filePath: string) {
const rdoc = await record.get(rid);
const [pdoc, udoc] = await Promise.all([
problem.get(rdoc.domainId, rdoc.pid),
user.getById(rdoc.domainId, rdoc.uid),
]);
if (!udoc.own(pdoc, PERM.PERM_EDIT_PROBLEM_SELF) && !udoc.hasPerm(PERM.PERM_EDIT_PROBLEM)) throw new ForbiddenError();
if (pdoc.reference) throw new ProblemIsReferencedError('edit files');
const stat = await fs.stat(filePath);
if ((pdoc.data?.length || 0)
+ (pdoc.additional_file?.length || 0)
>= system.get('limit.problem_files_max')) {
throw new FileLimitExceededError('count');
}
const size = Math.sum(
(pdoc.data || []).map((i) => i.size),
(pdoc.additional_file || []).map((i) => i.size),
stat.size,
);
if (size >= system.get('limit.problem_files_max_size')) {
throw new FileLimitExceededError('size');
}
await problem.addTestdata(pdoc.domainId, pdoc.docId, sanitize(filename), fs.createReadStream(filePath), udoc._id);
}
export class JudgeFileUpdateHandler extends Handler {
@post('rid', Types.ObjectId)
@post('name', Types.Filename)
async post(domainId: string, rid: ObjectId, filename: string) {
if (!this.request.files.file) throw new ValidationError('file');
await processJudgeFileCallback(rid, filename, this.request.files.file.filepath);
this.response.body = { ok: 1 };
}
}
class JudgeConnectionHandler extends ConnectionHandler {
category = '#judge';
processing: Task[] = [];
closed = false;
query: any = { type: 'judge' };
query: any = { type: { $in: ['judge', 'generate'] } };
rdocs: Record<string, RecordDoc> = {};
ip: string;
concurrency = 1;
@@ -282,6 +324,7 @@ class JudgeConnectionHandler extends ConnectionHandler {
export async function apply(ctx) {
ctx.Route('judge_files_download', '/judge/files', JudgeFilesDownloadHandler, builtin.PRIV.PRIV_JUDGE);
ctx.Route('judge_files_upload', '/judge/upload', JudgeFileUpdateHandler, builtin.PRIV.PRIV_JUDGE);
ctx.Route('judge_submission_download', '/judge/code', SubmissionDataDownloadHandler, builtin.PRIV.PRIV_JUDGE);
ctx.Connection('judge_conn', '/judge/conn', JudgeConnectionHandler, builtin.PRIV.PRIV_JUDGE);
}
+20 -9
View File
@@ -519,7 +519,7 @@ export class ProblemSubmitHandler extends ProblemDetailHandler {
}
if (pretest) {
if (setting.langs[lang]?.pretest) lang = setting.langs[lang].pretest as string;
if (!['default', 'fileio', 'remote_judge'].includes(this.response.body.pdoc.config?.type)) {
if (!['default', 'remote_judge'].includes(this.response.body.pdoc.config?.type)) {
throw new ProblemNotAllowPretestError('type');
}
}
@@ -543,7 +543,7 @@ export class ProblemSubmitHandler extends ProblemDetailHandler {
}
const rid = await record.add(
domainId, this.pdoc.docId, this.user._id, lang, code, true,
pretest ? { input, type: 'pretest' } : { contest: tid, files, type: tid ? 'contest' : 'judge' },
pretest ? { input, type: 'pretest' } : { contest: tid, files, type: 'judge' },
);
const rdoc = await record.get(domainId, rid);
if (!pretest) {
@@ -698,6 +698,12 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
} else this.response.template = 'problem_files.html';
}
async post() {
if (this.args.operation === 'get_links') return;
if (this.pdoc.reference) throw new ProblemIsReferencedError('edit files');
if (!this.user.own(this.pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
}
@post('files', Types.Set)
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postGetLinks(domainId: string, files: Set<string>, type = 'testdata') {
@@ -730,10 +736,8 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
@post('filename', Types.Filename, true)
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postUploadFile(domainId: string, filename: string, type = 'testdata') {
if (this.pdoc.reference) throw new ProblemIsReferencedError('edit files');
if (!this.request.files.file) throw new ValidationError('file');
filename ||= this.request.files.file.originalFilename || String.random(16);
if (!this.user.own(this.pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
const files = [];
if (filename.endsWith('.zip') && type === 'testdata') {
let zip: AdmZip;
@@ -744,7 +748,7 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
}
const entries = zip.getEntries();
for (const entry of entries) {
if (!entry.name) continue;
if (!entry.name || entry.isDirectory) continue;
files.push({
type,
name: sanitize(entry.name),
@@ -788,9 +792,7 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
@post('newNames', Types.ArrayOf(Types.Filename))
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postRenameFiles(domainId: string, files: string[], newNames: string[], type = 'testdata') {
if (this.pdoc.reference) throw new ProblemIsReferencedError('rename files');
if (files.length !== newNames.length) throw new ValidationError('files', 'newNames');
if (!this.user.own(this.pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
await Promise.all(files.map(async (file, index) => {
const newName = newNames[index];
if (type === 'testdata') await problem.renameTestdata(domainId, this.pdoc.docId, file, newName, this.user._id);
@@ -802,12 +804,21 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
@post('files', Types.ArrayOf(Types.Filename))
@post('type', Types.Range(['testdata', 'additional_file']), true)
async postDeleteFiles(domainId: string, files: string[], type = 'testdata') {
if (this.pdoc.reference) throw new ProblemIsReferencedError('delete files');
if (!this.user.own(this.pdoc, PERM.PERM_EDIT_PROBLEM_SELF)) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
if (type === 'testdata') await problem.delTestdata(domainId, this.pdoc.docId, files, this.user._id);
else await problem.delAdditionalFile(domainId, this.pdoc.docId, files, this.user._id);
this.back();
}
@post('std', Types.Filename)
@post('gen', Types.Filename)
async postGenerateTestdata(domainId: string, std: string, gen: string) {
if (!this.pdoc.data.find((i) => i.name === std)) throw new BadRequestError();
if (!this.pdoc.data.find((i) => i.name === gen)) throw new BadRequestError();
const rid = await record.add(domainId, this.pdoc.docId, this.user._id, '_', `${gen}\n${std}`, true, {
type: 'generate',
});
this.response.redirect = this.url('record_detail', { rid });
}
}
export class ProblemFileDownloadHandler extends ProblemDetailHandler {