Browse Source

Add non-traditional problem support; add problem additional_file

master
Menci 8 years ago
parent
commit
6bb873810e
  1. 115
      models/file.js
  2. 13
      models/judge_state.js
  3. 109
      models/problem.js
  4. 64
      models/testdata.js
  5. 30
      modules/api.js
  6. 63
      modules/problem.js
  7. 9
      modules/submission.js
  8. 0
      uploads/additional_file/.gitkeep
  9. 0
      uploads/answer/.gitkeep
  10. 0
      uploads/testdata/.gitkeep
  11. 2
      utility.js
  12. 22
      views/problem.ejs
  13. 45
      views/problem_data.ejs
  14. 22
      views/statistics.ejs
  15. 13
      views/submission_content.ejs
  16. 3
      views/submissions.ejs
  17. 5
      views/submissions_item.ejs

115
models/file.js

@ -0,0 +1,115 @@
/*
* This file is part of SYZOJ.
*
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
*
* SYZOJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* SYZOJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with SYZOJ. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
let Sequelize = require('sequelize');
let db = syzoj.db;
let model = db.define('file', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
type: { type: Sequelize.STRING(80) },
md5: { type: Sequelize.STRING(80), unique: true }
}, {
timestamps: false,
tableName: 'file',
indexes: [
{
fields: ['type'],
},
{
fields: ['md5'],
}
]
});
let Model = require('./common');
class File extends Model {
static create(val) {
return File.fromRecord(File.model.build(Object.assign({
type: '',
md5: ''
}, val)));
}
getPath() {
return File.resolvePath(this.type, this.md5);
}
static resolvePath(type, md5) {
return syzoj.utils.resolvePath(syzoj.config.upload_dir, type, md5);
}
static async upload(path, type) {
let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(path);
if (buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。');
try {
let AdmZip = require('adm-zip');
let zip = new AdmZip(buf);
this.unzipSize = 0;
for (let x of zip.getEntries()) this.unzipSize += x.header.size;
} catch (e) {
this.unzipSize = null;
}
let key = syzoj.utils.md5(buf);
await fs.moveAsync(path, File.resolvePath(type, key), { overwrite: true });
let file = await File.findOne({ where: { md5: key } });
if (!file) {
file = await File.create({
type: type,
md5: key
});
await file.save();
}
return file;
}
async getUnzipSize() {
if (this.unzipSize === undefined) {
try {
let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(this.getPath());
let AdmZip = require('adm-zip');
let zip = new AdmZip(buf);
this.unzipSize = 0;
for (let x of zip.getEntries()) this.unzipSize += x.header.size;
} catch (e) {
this.unzipSize = null;
}
}
if (this.unzipSize === null) throw new ErrorMessage('无效的 ZIP 文件。');
else return this.unzipSize;
}
getModel() { return model; }
}
File.model = model;
module.exports = File;

13
models/judge_state.js

@ -28,6 +28,8 @@ let Contest = syzoj.model('contest');
let model = db.define('judge_state', { let model = db.define('judge_state', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
// The data zip's md5 if it's a submit-answer problem
code: { type: Sequelize.TEXT('medium') }, code: { type: Sequelize.TEXT('medium') },
language: { type: Sequelize.STRING(20) }, language: { type: Sequelize.STRING(20) },
@ -163,8 +165,11 @@ class JudgeState extends Model {
this.score = result.score; this.score = result.score;
this.pending = result.pending; this.pending = result.pending;
this.status = result.status; this.status = result.status;
if (this.language) {
// language is empty if it's a submit-answer problem
this.total_time = result.total_time; this.total_time = result.total_time;
this.max_memory = result.max_memory; this.max_memory = result.max_memory;
}
this.result = result; this.result = result;
} }
@ -203,8 +208,11 @@ class JudgeState extends Model {
this.status = 'Waiting'; this.status = 'Waiting';
this.score = 0; this.score = 0;
if (this.language) {
// language is empty if it's a submit-answer problem
this.total_time = 0; this.total_time = 0;
this.max_memory = 0; this.max_memory = 0;
}
this.pending = true; this.pending = true;
this.result = { status: "Waiting", total_time: 0, max_memory: 0, score: 0, case_num: 0, compiler_output: "", pending: true }; this.result = { status: "Waiting", total_time: 0, max_memory: 0, score: 0, case_num: 0, compiler_output: "", pending: true };
await this.save(); await this.save();
@ -233,6 +241,11 @@ class JudgeState extends Model {
}); });
} }
async getProblemType() {
await this.loadRelationships();
return this.problem.type;
}
getModel() { return model; } getModel() { return model; }
} }

109
models/problem.js

@ -144,6 +144,56 @@ FROM `judge_state` `outer_table` \
WHERE \ WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \ `problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `submit_time` ASC \ ORDER BY `submit_time` ASC \
',
min:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`max_memory` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `max_memory` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
',
max:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`max_memory` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `max_memory` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` DESC \
' '
}; };
@ -151,7 +201,7 @@ let Sequelize = require('sequelize');
let db = syzoj.db; let db = syzoj.db;
let User = syzoj.model('user'); let User = syzoj.model('user');
let TestData = syzoj.model('testdata'); let File = syzoj.model('file');
let model = db.define('problem', { let model = db.define('problem', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
@ -182,13 +232,8 @@ let model = db.define('problem', {
time_limit: { type: Sequelize.INTEGER }, time_limit: { type: Sequelize.INTEGER },
memory_limit: { type: Sequelize.INTEGER }, memory_limit: { type: Sequelize.INTEGER },
testdata_id: { testdata_id: { type: Sequelize.INTEGER },
type: Sequelize.INTEGER, additional_file_id: { type: Sequelize.INTEGER },
references: {
model: 'file',
key: 'id'
}
},
ac_num: { type: Sequelize.INTEGER }, ac_num: { type: Sequelize.INTEGER },
submit_num: { type: Sequelize.INTEGER }, submit_num: { type: Sequelize.INTEGER },
@ -196,7 +241,12 @@ let model = db.define('problem', {
file_io: { type: Sequelize.BOOLEAN }, file_io: { type: Sequelize.BOOLEAN },
file_io_input_name: { type: Sequelize.TEXT }, file_io_input_name: { type: Sequelize.TEXT },
file_io_output_name: { type: Sequelize.TEXT } file_io_output_name: { type: Sequelize.TEXT },
type: {
type: Sequelize.ENUM,
values: ['traditional', 'submit-answer', 'interaction']
}
}, { }, {
timestamps: false, timestamps: false,
tableName: 'problem', tableName: 'problem',
@ -234,14 +284,17 @@ class Problem extends Model {
file_io: false, file_io: false,
file_io_input_name: '', file_io_input_name: '',
file_io_output_name: '' file_io_output_name: '',
type: ''
}, val))); }, val)));
} }
async loadRelationships() { async loadRelationships() {
this.user = await User.fromID(this.user_id); this.user = await User.fromID(this.user_id);
this.publicizer = await User.fromID(this.publicizer_id); this.publicizer = await User.fromID(this.publicizer_id);
this.testdata = await TestData.fromID(this.testdata_id); this.testdata = await File.fromID(this.testdata_id);
this.additional_file = await File.fromID(this.additional_file_id);
} }
async isAllowedEditBy(user) { async isAllowedEditBy(user) {
@ -263,35 +316,14 @@ class Problem extends Model {
return user.is_admin; return user.is_admin;
} }
async updateTestdata(path) { async updateFile(path, type) {
let fs = Promise.promisifyAll(require('fs-extra')); let file = await File.upload(path, type);
let buf = await fs.readFileAsync(path);
if (buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('测试数据太大。');
let key = syzoj.utils.md5(buf);
await fs.moveAsync(path, TestData.resolvePath(key), { overwrite: true });
if (this.testdata_id) {
let tmp = this.testdata_id;
this.testdata_id = null;
await this.save();
let file = await TestData.fromID(tmp);
if (file) await file.destroy();
}
let filename = `test_data_${this.id}.zip`;
let file = await TestData.findOne({ where: { filename: filename } });
if (file) await file.destroy();
file = await TestData.create({ if (type === 'testdata') {
filename: filename,
md5: key
});
await file.save();
this.testdata_id = file.id; this.testdata_id = file.id;
} else if (type === 'additional_file') {
this.additional_file_id = file.id;
}
await this.save(); await this.save();
} }
@ -453,7 +485,6 @@ class Problem extends Model {
await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id); await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id);
await db.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); await db.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await db.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id); await db.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await db.query('UPDATE `file` SET `filename` = ' + `"test_data_${id}.zip"` + ' WHERE `filename` = ' + `"test_data_${this.id}.zip"`);
let Contest = syzoj.model('contest'); let Contest = syzoj.model('contest');
let contests = await Contest.all(); let contests = await Contest.all();

64
models/testdata.js

@ -1,64 +0,0 @@
/*
* This file is part of SYZOJ.
*
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
*
* SYZOJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* SYZOJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with SYZOJ. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
let Sequelize = require('sequelize');
let db = syzoj.db;
let model = db.define('file', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
filename: { type: Sequelize.STRING(80), unique: true },
md5: { type: Sequelize.STRING(80), unique: true }
}, {
timestamps: false,
tableName: 'file',
indexes: [
{
fields: ['filename'],
},
{
fields: ['md5'],
}
]
});
let Model = require('./common');
class TestData extends Model {
static create(val) {
return TestData.fromRecord(TestData.model.build(Object.assign({
filename: '',
md5: ''
}, val)));
}
getPath() {
return TestData.resolvePath(this.md5);
}
static resolvePath(md5) {
return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', md5);
}
getModel() { return model; }
}
TestData.model = model;
module.exports = TestData;

30
modules/api.js

@ -23,7 +23,7 @@ let User = syzoj.model('user');
let Problem = syzoj.model('problem'); let Problem = syzoj.model('problem');
let WaitingJudge = syzoj.model('waiting_judge'); let WaitingJudge = syzoj.model('waiting_judge');
let JudgeState = syzoj.model('judge_state'); let JudgeState = syzoj.model('judge_state');
let TestData = syzoj.model('testdata'); let File = syzoj.model('file');
function setLoginCookie(username, password, res) { function setLoginCookie(username, password, res) {
res.cookie('login', JSON.stringify([username, password])); res.cookie('login', JSON.stringify([username, password]));
@ -109,6 +109,18 @@ app.get('/api/waiting_judge', async (req, res) => {
}); });
if (judge_state) { if (judge_state) {
await judge_state.loadRelationships();
await judge_state.problem.loadRelationships();
if (judge_state.problem.type === 'submit-answer') {
res.send({
have_task: 1,
judge_id: judge_state.id,
answer_file: judge_state.code,
testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '',
problem_type: judge_state.problem.type
});
} else {
res.send({ res.send({
have_task: 1, have_task: 1,
judge_id: judge_state.id, judge_id: judge_state.id,
@ -119,8 +131,10 @@ app.get('/api/waiting_judge', async (req, res) => {
memory_limit: judge_state.problem.memory_limit, memory_limit: judge_state.problem.memory_limit,
file_io: judge_state.problem.file_io, file_io: judge_state.problem.file_io,
file_io_input_name: judge_state.problem.file_io_input_name, file_io_input_name: judge_state.problem.file_io_input_name,
file_io_output_name: judge_state.problem.file_io_output_name file_io_output_name: judge_state.problem.file_io_output_name,
problem_type: judge_state.problem.type
}); });
}
} else { } else {
res.send({ have_task: 0 }); res.send({ have_task: 0 });
} }
@ -145,9 +159,17 @@ app.post('/api/update_judge/:id', async (req, res) => {
} }
}); });
app.get('/static/uploads/:md5', async (req, res) => { app.get('/static/uploads/testdata/:md5', async (req, res) => {
try {
res.sendFile(File.resolvePath('testdata', req.params.md5));
} catch (e) {
res.status(500).send(e);
}
});
app.get('/static/uploads/answer/:md5', async (req, res) => {
try { try {
res.sendFile(TestData.resolvePath(req.params.md5)); res.sendFile(File.resolvePath('answer', req.params.md5));
} catch (e) { } catch (e) {
res.status(500).send(e); res.status(500).send(e);
} }

63
modules/problem.js

@ -464,7 +464,7 @@ app.get('/problem/:id/data', async (req, res) => {
} }
}); });
app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) => { app.post('/problem/:id/data', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let problem = await Problem.fromID(id); let problem = await Problem.fromID(id);
@ -480,11 +480,22 @@ app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) =>
problem.file_io_input_name = req.body.file_io_input_name; problem.file_io_input_name = req.body.file_io_input_name;
problem.file_io_output_name = req.body.file_io_output_name; problem.file_io_output_name = req.body.file_io_output_name;
if (problem.type === 'submit-answer' && req.body.type !== 'submit-answer' || problem.type !== 'submit-answer' && req.body.type === 'submit-answer') {
if (await JudgeState.count({ problem_id: id }) !== 0) {
throw new ErrorMessage('已有提交的题目不允许在提交答案和非提交答案之间更改。');
}
}
problem.type = req.body.type;
let validateMsg = await problem.validate(); let validateMsg = await problem.validate();
if (validateMsg) throw new ErrorMessage('无效的题目数据配置。', null, validateMsg); if (validateMsg) throw new ErrorMessage('无效的题目数据配置。', null, validateMsg);
if (req.file) { if (req.files['testdata']) {
await problem.updateTestdata(req.file.path); await problem.updateFile(req.files['testdata'][0].path, 'testdata');
}
if (req.files['additional_file']) {
await problem.updateFile(req.files['additional_file'][0].path, 'additional_file');
} }
await problem.save(); await problem.save();
@ -529,21 +540,37 @@ app.get('/problem/:id/dis_public', async (req, res) => {
await setPublic(req, res, false); await setPublic(req, res, false);
}); });
app.post('/problem/:id/submit', async (req, res) => { app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let problem = await Problem.fromID(id); let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。'); if (!problem) throw new ErrorMessage('无此题目。');
if (!syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。'); if (problem.type !== 'submit-answer' && !syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。');
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) }); if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });
let judge_state = await JudgeState.create({ let judge_state;
if (problem.type === 'submit-answer') {
let File = syzoj.model('file');
let file = await File.upload(req.files['answer'][0].path, 'answer');
let size = await file.getUnzipSize();
if (!file.md5) throw new ErrorMessage('上传答案文件失败。');
judge_state = await JudgeState.create({
code: file.md5,
max_memory: size,
language: '',
user_id: res.locals.user.id,
problem_id: req.params.id
});
} else {
judge_state = await JudgeState.create({
code: req.body.code, code: req.body.code,
language: req.body.language, language: req.body.language,
user_id: res.locals.user.id, user_id: res.locals.user.id,
problem_id: req.params.id problem_id: req.params.id
}); });
}
let contest_id = parseInt(req.query.contest_id), redirectToContest = false; let contest_id = parseInt(req.query.contest_id), redirectToContest = false;
if (contest_id) { if (contest_id) {
@ -579,7 +606,7 @@ app.post('/problem/:id/submit', async (req, res) => {
} }
}); });
app.get('/problem/:id/download', async (req, res) => { app.get('/problem/:id/download/testdata', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
let problem = await Problem.fromID(id); let problem = await Problem.fromID(id);
@ -601,6 +628,28 @@ app.get('/problem/:id/download', async (req, res) => {
} }
}); });
app.get('/problem/:id/download/additional_file', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
await problem.loadRelationships();
if (!problem.additional_file) throw new ErrorMessage('无附加文件。');
res.download(problem.additional_file.getPath(), `additional_file_${id}.zip`);
} catch (e) {
syzoj.log(e);
res.status(404);
res.render('error', {
err: e
});
}
});
app.get('/problem/:id/statistics/:type', async (req, res) => { app.get('/problem/:id/statistics/:type', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);

9
modules/submission.js

@ -42,7 +42,10 @@ app.get('/submissions', async (req, res) => {
} }
}; };
if (req.query.language) where.language = req.query.language; if (req.query.language) {
if (req.query.language === 'submit-answer') where.language = '';
else where.language = req.query.language;
}
if (req.query.status) where.status = { $like: req.query.status + '%' }; if (req.query.status) where.status = { $like: req.query.status + '%' };
where.type = { $ne: 1 }; where.type = { $ne: 1 };
@ -136,8 +139,10 @@ app.get('/submission/:id', async (req, res) => {
await judge.loadRelationships(); await judge.loadRelationships();
if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length; judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
}
judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user); judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user);
judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user); judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user);
judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user); judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user);
@ -181,8 +186,10 @@ app.get('/submission/:id/ajax', async (req, res) => {
await judge.loadRelationships(); await judge.loadRelationships();
if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length; judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
}
judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user); judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user);
judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user); judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user);
judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user); judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user);

0
uploads/testdata/.placeholder → uploads/additional_file/.gitkeep

0
uploads/answer/.gitkeep

0
uploads/testdata/.gitkeep vendored

2
utility.js

@ -195,7 +195,7 @@ module.exports = {
gravatar(email, size) { gravatar(email, size) {
return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn'); return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn');
}, },
parseTestData(filename) { parseTestdata(filename) {
let zip = new AdmZip(filename); let zip = new AdmZip(filename);
let list = zip.getEntries().filter(e => !e.isDirectory).map(e => e.entryName); let list = zip.getEntries().filter(e => !e.isDirectory).map(e => e.entryName);
let res = []; let res = [];

22
views/problem.ejs

@ -88,8 +88,9 @@ if (contest) {
<a class="small ui primary button" href="#submit_code">提交</a> <a class="small ui primary button" href="#submit_code">提交</a>
<a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['submissions'], { problem_id: problem.id }) %>">提交记录</a> <a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['submissions'], { problem_id: problem.id }) %>">提交记录</a>
<a class="small ui orange button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', 'fastest']) %>">统计</a> <a class="small ui orange button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', 'fastest']) %>">统计</a>
<a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download']) %>">下载测试数据</a> <% if (problem.testdata) { %><a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'testdata']) %>">下载测试数据</a><% } %>
<% } %> <% } %>
<% if (problem.additional_file) { %><a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'additional_file']) %>">下载附加文件</a><% } %>
</div> </div>
<% if (!contest) { %> <% if (!contest) { %>
<div class="ui buttons right floated"> <div class="ui buttons right floated">
@ -187,7 +188,15 @@ if (contest) {
if (contest) formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit'], { contest_id: contest.id }); if (contest) formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit'], { contest_id: contest.id });
else formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit']); else formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit']);
%> %>
<form class="ui form" action="<%= formUrl %>" method="post" onsubmit="return submit_code()" id="submit_code"> <form class="ui form" action="<%= formUrl %>" method="post" onsubmit="return submit_code()" id="submit_code" enctype="multipart/form-data">
<% if (problem.type === 'submit-answer') { %>
<div class="inline fields">
<div class="field" style="margin: 0 auto; ">
<label for="answer">上传答案(请使用 ZIP 格式压缩)</label>
<input type="file" id="answer" name="answer">
</div>
</div>
<% } else { %>
<input name="language" type="hidden" id="form"> <input name="language" type="hidden" id="form">
<input name="code" type="hidden"> <input name="code" type="hidden">
<div class="ui grid"> <div class="ui grid">
@ -211,6 +220,7 @@ if (contest) {
<div id="editor" style="border: 1px solid #D4D4D5; "><% if (state) { %><%= state.code %><% } %></div> <div id="editor" style="border: 1px solid #D4D4D5; "><% if (state) { %><%= state.code %><% } %></div>
</div> </div>
</div> </div>
<% } %>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div> <div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div>
</form> </form>
</div> </div>
@ -218,6 +228,7 @@ if (contest) {
<% } %> <% } %>
</div> </div>
<% if (problem.type !== 'submit-answer') { %>
<script src="/libs/ace/ace.js"></script> <script src="/libs/ace/ace.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var editor = ace.edit("editor"); var editor = ace.edit("editor");
@ -254,4 +265,11 @@ $(function () {
}); });
}); });
</script> </script>
<% } else { %>
<script>
function submit_code() {
if ($('#answer')[0].files.length === 0) return false;
}
</script>
<% } %>
<% include footer %> <% include footer %>

45
views/problem_data.ejs

@ -14,7 +14,7 @@ let subtaskType = {
<% if (problem.testdata) { %> <% if (problem.testdata) { %>
<% <%
try { try {
let list = syzoj.utils.parseTestData(problem.testdata.getPath()); let list = syzoj.utils.parseTestdata(problem.testdata.getPath());
%> %>
<% if (list.spj) { %> <% if (list.spj) { %>
<p>评测方式:Special Judge</p> <p>评测方式:Special Judge</p>
@ -51,7 +51,14 @@ let subtaskType = {
<% } %> <% } %>
</div> </div>
<div class="nine wide column"> <div class="nine wide column">
<form class="ui form" method="post" enctype="multipart/form-data"> <form class="ui form" method="post" enctype="multipart/form-data" onsubmit="return checkSubmit()">
<input type="hidden" name="type" value="<%= problem.type %>">
<div class="ui pointing secondary menu" id="problem-type-tab" style="margin-top: -10px; ">
<a class="<%= problem.type === 'traditional' ? 'active ' : '' %>item" data-tab="traditional">传统</a>
<a class="<%= problem.type === 'interaction' ? 'active ' : '' %>item" data-tab="interaction">交互</a>
<a class="<%= problem.type === 'submit-answer' ? 'active ' : '' %>item" data-tab="submit-answer">提交答案</a>
</div>
<div class="ui <%= problem.type !== 'submit-answer' ? 'active ' : '' %>tab" data-tab="traditional" data-tab="interaction">
<div class="two fields"> <div class="two fields">
<div class="field"> <div class="field">
<label for="doc-ds-ipt-1">时间限制(单位: ms)</label> <label for="doc-ds-ipt-1">时间限制(单位: ms)</label>
@ -115,10 +122,20 @@ let subtaskType = {
</div> </div>
</div> </div>
<% } %> <% } %>
</div>
<div class="ui <%= problem.type === 'submit-answer' ? 'active ' : '' %>tab" data-tab="submit-answer" style="margin-bottom: 10px; ">
<b>为了避免系统出错,已有提交的题目不允许在提交答案和非提交答案之间更改。</b><br>
提交答案题目不需要设置时间限制、空间限制以及 IO 方式。<br>
提交时使用的答案文件名需要与测试数据中每个测试点输出文件相同,且后缀名为 <code>.out</code>。
</div>
<div class="field"> <div class="field">
<label for="testdata"><% if (!problem.testdata_id) { %>上传测试数据<% } else { %>更新测试数据<% } %></label> <label for="testdata">上传测试数据(请使用 ZIP 格式)</label>
<input type="file" id="testdata" name="testdata"> <input type="file" id="testdata" name="testdata">
</div> </div>
<div class="field">
<label for="additional_file">上传附加文件(请使用 ZIP 格式)</label>
<input type="file" id="additional_file" name="additional_file">
</div>
<button type="submit" class="ui button">提交</button> <button type="submit" class="ui button">提交</button>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a> <a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a>
</form> </form>
@ -135,7 +152,7 @@ function goDisable() {
document.getElementById('file-io-output-name').disabled = true; document.getElementById('file-io-output-name').disabled = true;
} }
$(document).ready(function () { $(function () {
$('#file-io-input-name').on('input keyup change', function (e) { $('#file-io-input-name').on('input keyup change', function (e) {
var prob = $('#file-io-input-name').val(); var prob = $('#file-io-input-name').val();
if (prob.lastIndexOf('.') !== -1) prob = prob.substring(0, prob.lastIndexOf('.')); if (prob.lastIndexOf('.') !== -1) prob = prob.substring(0, prob.lastIndexOf('.'));
@ -146,6 +163,26 @@ $(document).ready(function () {
$('#file-io-output-name').val($('#file-io-output-name').attr('placeholder')); $('#file-io-output-name').val($('#file-io-output-name').attr('placeholder'));
} }
}); });
$('#problem-type-tab .item').tab();
$('a[data-tab="traditional"]').click(function () {
$('input[name=type]').val('traditional');
if ($('div[data-tab="interaction"]').attr('data-tab', 'traditional').length) $('a[data-tab="traditional"]').click();
}); });
$('a[data-tab="interaction"]').click(function () {
$('input[name=type]').val('interaction');
if ($('div[data-tab="traditional"]').attr('data-tab', 'interaction').length) $('a[data-tab="interaction"]').click();
});
$('a[data-tab="submit-answer"]').click(function () {
$('input[name=type]').val('submit-answer');
});
});
function checkSubmit() {
;
}
</script> </script>
<% include footer %> <% include footer %>

22
views/statistics.ejs

@ -1,11 +1,13 @@
<% <%
this.title = '统计'; this.title = '统计';
let types = { let types = {
fastest: '最快', fastest: problem.type === 'submit-answer' ? null : '最快',
slowest: '最慢', slowest: problem.type === 'submit-answer' ? null : '最慢',
shortest: '最短', shortest: problem.type === 'submit-answer' ? null : '最短',
longest: '最长', longest: problem.type === 'submit-answer' ? null : '最长',
earliest: '最早' earliest: '最早',
min: problem.type === 'submit-answer' ? '最小' : '最小内存',
max: problem.type === 'submit-answer' ? '最大' : '最大内存'
}; };
%> %>
<% include header %> <% include header %>
@ -38,7 +40,7 @@ function getColorOfScore(score) {
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
<% for (let type in types) { %> <% for (let type in types) { %>
<% if (type !== statistics.type) { %> <% if (type !== statistics.type && types[type] !== null) { %>
<a class="item" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', type]) %>"><%= types[type] %></a> <a class="item" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', type]) %>"><%= types[type] %></a>
<% } %> <% } %>
<% } %> <% } %>
@ -54,9 +56,13 @@ function getColorOfScore(score) {
<th>题目</th> <th>题目</th>
<th>状态</th> <th>状态</th>
<th>分数</th> <th>分数</th>
<% if (problem.type !== 'submit-answer') { %>
<th>总时间</th> <th>总时间</th>
<th>内存</th> <th>内存</th>
<th>代码</th> <th>代码</th>
<% } else { %>
<th>答案文件</th>
<% } %>
<th>提交者</th> <th>提交者</th>
<th>提交时间</th> <th>提交时间</th>
</tr> </tr>
@ -74,9 +80,13 @@ function getColorOfScore(score) {
</span> </span>
</a></td> </a></td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td> <td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td>
<% if (problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td> <td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td> <td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td> <td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% } else { %>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td> <td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td> <td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
</tr> </tr>

13
views/submission_content.ejs

@ -1,3 +1,4 @@
<% include util %>
<% <%
// Sanitize judge results for backward compatibility and clarity // Sanitize judge results for backward compatibility and clarity
if (!judge.result.subtasks) { if (!judge.result.subtasks) {
@ -38,9 +39,13 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %> <% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<th>分数</th> <th>分数</th>
<% } %> <% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<th>总时间</th> <th>总时间</th>
<th>内存</th> <th>内存</th>
<th>代码</th> <th>代码</th>
<% } else { %>
<th>文件大小</th>
<% } %>
<th>提交者</th> <th>提交者</th>
<th>提交时间</th> <th>提交时间</th>
</tr> </tr>
@ -56,6 +61,7 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %> <% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td> <td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td>
<% } %> <% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td> <td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td> <td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %> <% if (judge.allowedSeeCode) { %>
@ -63,6 +69,9 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% } else { %> <% } else { %>
<td><%= syzoj.config.languages[judge.language].show %> / 隐藏 %></td> <td><%= syzoj.config.languages[judge.language].show %> / 隐藏 %></td>
<% } %> <% } %>
<% } else { %>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td> <td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td> <td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
</tr> </tr>
@ -76,7 +85,7 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
} }
window.applyTextFit(); window.applyTextFit();
</script> </script>
<% if (judge.allowedSeeCode) { %> <% if (judge.problem.type !== 'submit-answer' && judge.allowedSeeCode) { %>
<div class="ui existing segment" style="position: relative; "> <div class="ui existing segment" style="position: relative; ">
<% if (judge.allowedRejudge) { %> <% if (judge.allowedRejudge) { %>
<a id="rejudge-button" href="<%= syzoj.utils.makeUrl(['submission', judge.id, 'rejudge']) %>" class="ui button" style="position: absolute; top: 0px; right: -4px; border-top-left-radius: 0; border-bottom-right-radius: 0; <% if (judge.pending) { %>display: none; <% } %>">重新评测</a> <a id="rejudge-button" href="<%= syzoj.utils.makeUrl(['submission', judge.id, 'rejudge']) %>" class="ui button" style="position: absolute; top: 0px; right: -4px; border-top-left-radius: 0; border-bottom-right-radius: 0; <% if (judge.pending) { %>display: none; <% } %>">重新评测</a>
@ -96,7 +105,7 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
}); });
</script> </script>
<% } %> <% } %>
<% if (judge.result.compiler_output && judge.status === 'Compile Error' && judge.allowedSeeCode) { %> <% if (judge.problem.type !== 'submit-answer' && judge.result.compiler_output && judge.status === 'Compile Error' && judge.allowedSeeCode) { %>
<h3 class="ui header">编译信息</h3> <h3 class="ui header">编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.compiler_output) %></code></pre></div> <div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.compiler_output) %></code></pre></div>
<% } else if (judge.result.spj_compiler_output) { %> <% } else if (judge.result.spj_compiler_output) { %>

3
views/submissions.ejs

@ -21,6 +21,7 @@
<div class="default text"></div> <div class="default text"></div>
<div class="menu"> <div class="menu">
<div class="item" data-value="">不限</div> <div class="item" data-value="">不限</div>
<div class="item" data-value="submit-answer">提交答案</div>
<% for (let lang in syzoj.config.languages) { %> <% for (let lang in syzoj.config.languages) { %>
<div class="item" data-value="<%= lang %>"><%= syzoj.config.languages[lang].show %></div> <div class="item" data-value="<%= lang %>"><%= syzoj.config.languages[lang].show %></div>
<% } %> <% } %>
@ -70,7 +71,7 @@
<th>分数</th> <th>分数</th>
<th>总时间</th> <th>总时间</th>
<th>内存</th> <th>内存</th>
<th>代码</th> <th>代码 / 答案文件</th>
<th>提交者</th> <th>提交者</th>
<th>提交时间</th> <th>提交时间</th>
</tr> </tr>

5
views/submissions_item.ejs

@ -19,6 +19,7 @@ textFit(e, { maxFontSize: 14 });
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %> <% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td> <td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td>
<% } %> <% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td> <td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td> <td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %> <% if (judge.allowedSeeCode) { %>
@ -26,6 +27,10 @@ textFit(e, { maxFontSize: 14 });
<% } else { %> <% } else { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / 隐藏</td> <td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / 隐藏</td>
<% } %> <% } %>
<% } else { %>
<td>-</td><td>-</td>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td> <td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %> <td><%= syzoj.utils.formatDate(judge.submit_time) %>
<% if (isPending(judge.status)) { %> <% if (isPending(judge.status)) { %>

Loading…
Cancel
Save