mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
feat: import problems from qduoj
This commit is contained in:
1 parent
72c0c4ffba
commit
a5004a7c1b
4 files changed
+180
-1
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.13.39",
|
||||
"version": "2.13.40",
|
||||
"bin": "bin/hydrooj.js",
|
||||
"main": "dist/loader.js",
|
||||
"typings": "dist/loader.d.ts",
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fs from 'fs-extra';
|
||||
import AdmZip from 'adm-zip';
|
||||
import yaml from 'js-yaml';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import { LocalProblemConfig } from 'hydrooj';
|
||||
import {
|
||||
Route, Handler, param, Types,
|
||||
} from 'hydrooj/dist/service/server';
|
||||
import { BadRequestError } from 'hydrooj/dist/error';
|
||||
import { streamToBuffer } from 'hydrooj/dist/utils';
|
||||
import { ProblemAdd } from 'hydrooj/dist/lib/ui';
|
||||
import * as file from 'hydrooj/dist/model/file';
|
||||
import * as problem from 'hydrooj/dist/model/problem';
|
||||
import { PERM } from 'hydrooj/dist/model/builtin';
|
||||
|
||||
const processing = {};
|
||||
fs.ensureDirSync('/tmp/hydro/import-qduoj');
|
||||
|
||||
class ImportQduojHandler extends Handler {
|
||||
async ImportFromFile(domainId: string, id: string, zipfile: Buffer) {
|
||||
const zip = new AdmZip(zipfile);
|
||||
const tmp = path.resolve(os.tmpdir(), 'hydro', 'import-qduoj', id);
|
||||
await new Promise((resolve, reject) => {
|
||||
zip.extractAllToAsync(tmp, true, (err) => (err ? resolve() : reject(err)));
|
||||
});
|
||||
try {
|
||||
const folders = await fs.readdir(tmp);
|
||||
for (const folder of folders) {
|
||||
const buf = await fs.readFile(path.join(tmp, folder, 'problem.json'));
|
||||
const pdoc = JSON.parse(buf.toString());
|
||||
const content = [
|
||||
...pdoc.description?.value
|
||||
? [
|
||||
'<h2>Description</h2>',
|
||||
pdoc.description.value,
|
||||
] : [],
|
||||
...pdoc.input_description?.value
|
||||
? [
|
||||
'<h2>Input Description</h2>',
|
||||
pdoc.input_description.value,
|
||||
] : [],
|
||||
...pdoc.output_description?.value
|
||||
? [
|
||||
'<h2>Output Description</h2>',
|
||||
pdoc.output_description.value,
|
||||
] : [],
|
||||
...(pdoc.samples as any[]).map((sample, i) => [
|
||||
`<h2>Sample Input ${i + 1}</h2><pre>`,
|
||||
sample.input,
|
||||
`</pre><h2>Sample Output ${i + 1}</h2><pre>`,
|
||||
sample.output,
|
||||
'</pre>',
|
||||
].join('\n')),
|
||||
...pdoc.hint?.value
|
||||
? [
|
||||
'<h2>Hint</h2>',
|
||||
pdoc.hint.value,
|
||||
] : [],
|
||||
...pdoc.source ? ['<h2>Source</h2>', pdoc.source] : [],
|
||||
].join('\n');
|
||||
const pid = await problem.add(domainId, pdoc.display_id, pdoc.title, content, this.user._id, pdoc.tags);
|
||||
const testdata = new AdmZip();
|
||||
const config: LocalProblemConfig = {
|
||||
time: `${pdoc.time_limit}ms`,
|
||||
memory: `${pdoc.memory_limit}m`,
|
||||
subtasks: [],
|
||||
};
|
||||
for (const tc of pdoc.test_case_score) {
|
||||
testdata.addLocalFile(path.join(tmp, 'testcase', tc.input_name));
|
||||
testdata.addLocalFile(path.join(tmp, 'testcase', tc.output_name));
|
||||
config.subtasks.push({
|
||||
score: tc.score,
|
||||
cases: [{
|
||||
input: tc.input_name,
|
||||
output: tc.output_name,
|
||||
}],
|
||||
});
|
||||
}
|
||||
testdata.addFile('config.yaml', Buffer.from(yaml.safeDump(config)));
|
||||
const f = path.resolve(os.tmpdir(), 'hydro', `${Math.random()}.zip`);
|
||||
await new Promise((resolve, reject) => {
|
||||
testdata.writeZip(f, (err) => (err ? resolve() : reject(err)));
|
||||
});
|
||||
try {
|
||||
await problem.setTestdata(domainId, pid, f);
|
||||
await problem.edit(domainId, pid, { html: true });
|
||||
} finally {
|
||||
await fs.unlink(f);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await fs.remove(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
@param('ufid', Types.ObjectID, true)
|
||||
async get(domainId: string, ufid?: ObjectID) {
|
||||
if (ufid) {
|
||||
if (processing[ufid.toHexString()]) {
|
||||
this.response.body = 'Processing';
|
||||
return;
|
||||
}
|
||||
const stat = await file.getMeta(ufid);
|
||||
if (stat.size > 128 * 1024 * 1024) throw new BadRequestError('File too large');
|
||||
const stream = await file.get(ufid);
|
||||
processing[ufid.toHexString()] = true;
|
||||
try {
|
||||
const buf = await streamToBuffer(stream);
|
||||
await this.ImportFromFile(domainId, ufid.toHexString(), buf);
|
||||
await file.del(ufid);
|
||||
} catch (e) {
|
||||
processing[ufid.toHexString()] = false;
|
||||
throw e;
|
||||
}
|
||||
} else this.response.template = 'problem_import_qduoj.html';
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
Route('problem_import_qduoj', '/problem/import/qduoj', ImportQduojHandler, PERM.PERM_CREATE_PROBLEM);
|
||||
ProblemAdd('problem_import_qduoj', {}, 'copy', 'From QDUOJ Export');
|
||||
}
|
||||
|
||||
global.Hydro.handler.qduImport = apply;
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@hydrooj/import-qduoj",
|
||||
"version": "1.0.0",
|
||||
"description": "Import QDUOJ problem export",
|
||||
"main": "package.json",
|
||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.4.16",
|
||||
"decode-html": "^2.0.0",
|
||||
"fs-extra": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bson": "^4.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"hydrooj": "^2.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"lint": "eslint . --ext ts --fix"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{% extends "layout/basic.html" %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="medium-9 columns">
|
||||
<div class="section">
|
||||
<div class="section__body">
|
||||
<form method="post" enctype="multipart/form-data" action="{{ url('file_upload') }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<p>{{ _('Upload zipfile') }}:
|
||||
<input type="hidden" name="title" value="QDUOJ_IMPORT">
|
||||
<input type="hidden" name="redirect" value="{{ url('problem_import_qduoj') }}">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="{{ _('Upload') }}" class="rounded primary button">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-3 columns">
|
||||
<div class="section side">
|
||||
<div class="section__body typo">
|
||||
<h2>{{ _('What is this?') }}</h2>
|
||||
<p>{{ _('With this feature, you can import problems that you can view from a site to here. Their title, content, tags and categories will be imported.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user