From 62e8153faadcad9625fb1800d28c03803f94605d Mon Sep 17 00:00:00 2001 From: Menci Date: Sun, 2 Jul 2017 15:48:17 +0800 Subject: [PATCH] Submit answer --- models/file.js | 10 +++-- models/problem.js | 2 +- modules/problem.js | 32 +++++++++++--- utility.js | 36 +++++++++++----- views/help.ejs | 11 ++++- views/problem.ejs | 86 ++++++++++++++++++++++++++++++++++--- views/problem_testcases.ejs | 22 +++++++--- 7 files changed, 163 insertions(+), 36 deletions(-) diff --git a/models/file.js b/models/file.js index 97244ff..2c575e0 100644 --- a/models/file.js +++ b/models/file.js @@ -56,10 +56,10 @@ class File extends Model { return syzoj.utils.resolvePath(syzoj.config.upload_dir, type, md5); } - static async upload(path, type, noLimit) { + static async upload(pathOrData, type, noLimit) { let fs = Promise.promisifyAll(require('fs-extra')); - let buf = await fs.readFileAsync(path); + let buf = Buffer.isBuffer(pathOrData) ? pathOrData : await fs.readFileAsync(pathOrData); if (!noLimit && buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。'); @@ -73,7 +73,11 @@ class File extends Model { } let key = syzoj.utils.md5(buf); - await fs.moveAsync(path, File.resolvePath(type, key), { overwrite: true }); + if (Buffer.isBuffer(pathOrData)) { + await fs.writeFileAsync(File.resolvePath(type, key), pathOrData); + } else { + await fs.moveAsync(pathOrData, File.resolvePath(type, key), { overwrite: true }); + } let file = await File.findOne({ where: { md5: key } }); if (!file) { diff --git a/models/problem.js b/models/problem.js index 38b8829..7b34acf 100644 --- a/models/problem.js +++ b/models/problem.js @@ -335,7 +335,7 @@ class Problem extends Model { await fs.moveAsync(path, dir + '.zip', { overwrite: true }); } - async uploadTestdataSingleFile(filename, filepath, size) { + async uploadTestdataSingleFile(filename, filepath, size, noLimit) { let dir = this.getTestdataPath(); let fs = Promise.promisifyAll(require('fs-extra')), path = require('path'); await fs.ensureDirAsync(dir); diff --git a/modules/problem.js b/modules/problem.js index a149fce..03a8c57 100644 --- a/modules/problem.js +++ b/modules/problem.js @@ -190,7 +190,6 @@ app.get('/problem/:id', async (req, res) => { problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user); problem.allowedManage = await problem.isAllowedManageBy(res.locals.user); - problem.specialJudge = await problem.hasSpecialJudge(); if (problem.is_public || problem.allowedEdit) { await syzoj.utils.markdown(problem, [ 'description', 'input_format', 'output_format', 'example', 'limit_and_hint' ]); @@ -203,10 +202,13 @@ app.get('/problem/:id', async (req, res) => { problem.tags = await problem.getTags(); await problem.loadRelationships(); + let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer'); + res.render('problem', { problem: problem, state: state, - lastLanguage: res.locals.user ? await res.locals.user.getLastSubmitLanguage() : null + lastLanguage: res.locals.user ? await res.locals.user.getLastSubmitLanguage() : null, + testcases: testcases }); } catch (e) { syzoj.log(e); @@ -461,7 +463,7 @@ app.get('/problem/:id/manage', async (req, res) => { await problem.loadRelationships(); - let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath()); + let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer'); res.render('problem_manage', { problem: problem, @@ -566,11 +568,27 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 let judge_state; if (problem.type === 'submit-answer') { - if (!req.files['answer']) throw new ErrorMessage('请上传答案文件。'); - if (req.files['answer'][0].size > syzoj.config.limit.submit_answer) throw new ErrorMessage('答案文件太大。'); + let pathOrData; + if (!req.files['answer']) { + // Submited by editor + try { + let files = JSON.parse(req.body.answer_by_editor); + let AdmZip = require('adm-zip'); + let zip = new AdmZip(); + for (let file of files) { + zip.addFile(file.filename, file.data); + } + pathOrData = zip.toBuffer(); + } catch (e) { + throw new ErrorMessage('无法解析提交数据。'); + } + } else { + if (req.files['answer'][0].size > syzoj.config.limit.submit_answer) throw new ErrorMessage('答案文件太大。'); + pathOrData = req.files['answer'][0].path; + } let File = syzoj.model('file'); - let file = await File.upload(req.files['answer'][0].path, 'answer'); + let file = await File.upload(pathOrData, 'answer'); let size = await file.getUnzipSize(); if (size > syzoj.config.limit.submit_answer) throw new ErrorMessage('答案文件太大。'); @@ -645,7 +663,7 @@ app.get('/problem/:id/testdata', async (req, res) => { if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); let testdata = await problem.listTestdata(); - let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath()); + let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer'); problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user) diff --git a/utility.js b/utility.js index c72a8ec..7398a67 100644 --- a/utility.js +++ b/utility.js @@ -196,7 +196,7 @@ module.exports = { gravatar(email, size) { return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn'); }, - async parseTestdata(dir) { + async parseTestdata(dir, submitAnswer) { if (!await syzoj.utils.isDir(dir)) return null; try { @@ -211,17 +211,21 @@ module.exports = { let parsedName = path.parse(file); if (parsedName.ext === '.in') { if (list.includes(`${parsedName.name}.out`)) { - res[0].cases.push({ + let o = { input: file, output: `${parsedName.name}.out` - }); + }; + if (submitAnswer) o.answer = `${parsedName.name}.out`; + res[0].cases.push(o); } if (list.includes(`${parsedName.name}.ans`)) { - res[0].cases.push({ + let o = { input: file, output: `${parsedName.name}.ans` - }); + }; + if (submitAnswer) o.answer = `${parsedName.name}.out`; + res[0].cases.push(o); } } } @@ -241,12 +245,19 @@ module.exports = { } else { let lines = (await fs.readFileAsync(path.join(dir, 'data_rule.txt'))).toString().split('\r').join('').split('\n').filter(x => x.length !== 0); - if (lines.length < 3) throw '无效的数据配置文件(data_rule.txt)。'; - - let input = lines[lines.length - 2]; - let output = lines[lines.length - 1]; + let input, output, answer; + if (submitAnswer) { + if (lines.length < 4) throw '无效的数据配置文件(data_rule.txt)。'; + input = lines[lines.length - 3]; + output = lines[lines.length - 2]; + answer = lines[lines.length - 1]; + } else { + if (lines.length < 3) throw '无效的数据配置文件(data_rule.txt)。'; + input = lines[lines.length - 2]; + output = lines[lines.length - 1]; + } - for (let s = 0; s < lines.length - 2; ++s) { + for (let s = 0; s < lines.length - (submitAnswer ? 3 : 2); ++s) { res[s] = {}; res[s].cases = []; let numbers = lines[s].split(' ').filter(x => x); @@ -265,6 +276,8 @@ module.exports = { output: output.replace('#', i) }; + if (submitAnswer) testcase.answer = answer.replace('#', i); + if (!list.includes(testcase.input)) throw `找不到文件 ${testcase.input}`; if (!list.includes(testcase.output)) throw `找不到文件 ${testcase.output}`; res[s].cases.push(testcase); @@ -274,9 +287,10 @@ module.exports = { res = res.filter(x => x.cases && x.cases.length !== 0); } - res.spj = list.includes('spj.js') || list.some(s => s.startsWith('spj_')); + res.spj = list.some(s => s.startsWith('spj_')); return res; } catch (e) { + console.log(e); return { error: e }; } }, diff --git a/views/help.ejs b/views/help.ejs index 1496d7b..d7a56b5 100644 --- a/views/help.ejs +++ b/views/help.ejs @@ -60,9 +60,18 @@ output#.out

注意:zip 包内没有一层文件夹,直接是上述文件。

如果没有 data_rule.txt,则评测系统会自动将 .in 文件与同名的 .out.ans 匹配。

+

如果需要配置提交答案题目的数据,请添加额外的一行,表示用户提交的文件名:

+
sum:30  1 2 3
+min:20  4 5 6
+mul:50  7 8 9 10
+
+input#.in
+output#.out
+submit#.out
+

Special Judge

如果需要使用 Special Judge 评分,请在数据包中添加 spj_LANG.xxx,其中 xxx 为任意后缀名,LANG 为所用语言的简称,可为 ccppcpp11csharphaskelljavaluanodejspascalpython2python3rubyvalavbnet

-

Special Judge 程序运行时,其目录下会有四个文件 inputuser_outanswercode,分别对应该测试点的输入文件、用户输出、该测试点的输出文件、用户的代码。

+

Special Judge 程序运行时,其目录下会有四个文件 inputuser_outanswercode,分别对应该测试点的输入文件、用户输出、该测试点的输出文件、用户的代码(对于非提交答案题目)。

Special Judge 程序运行完成后,应将该测试点的得分输出到标准输出stdout)中(范围为 0 到 100,将自动折合为测试点分数),并将提供给用户的额外信息输出到标准错误输出stderr)中。

diff --git a/views/problem.ejs b/views/problem.ejs index 50f8c08..ef5dd8a 100644 --- a/views/problem.ejs +++ b/views/problem.ejs @@ -18,6 +18,7 @@ if (contest) { background: transparent; } +

@@ -53,7 +54,7 @@ if (contest) { <% } %>

- 评测方式:<%= problem.specialJudge ? 'Special Judge' : '文本比较' %> + 评测方式:<%= testcases.spj ? 'Special Judge' : '文本比较' %> 题目类型:<%= { 'submit-answer': '答案提交', 'interaction': '交互', 'traditional': '传统' }[problem.type] %>
@@ -194,18 +195,84 @@ if (contest) { %>
<% if (problem.type === 'submit-answer') { %> -
+ <% + let cases = []; + for (let subtasks of testcases) { + for (let testcase of subtasks.cases) { + cases.push(testcase.answer); + } + } + %> + +
+
+ +
+
+ <% for (let i = 0; i < cases.length; i++) { %> +
+ <% } %> +
+ + +
- +
+
<% } else { %>
-