From 4b3c10b4eb27554fccc8b76621309837bd1b9c4f Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 13 Jun 2020 22:38:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=97=B6=E8=87=AA=E5=8A=A8=E8=AF=BB=E5=8F=96config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 1 + hydro/entry/common.js | 1 + hydro/handler/import.js | 12 +++++++++++- hydro/handler/problem.js | 19 ++++++++++++------- hydro/lib/readConfig.js | 19 +++++++++++++++++++ hydro/loader.js | 2 +- hydro/model/contest.js | 14 +++++--------- hydro/model/discussion.js | 2 +- hydro/model/file.js | 29 +++++++++++++++++------------ hydro/model/problem.js | 21 +++++++++++++-------- hydro/model/setting.js | 1 - hydro/model/user.js | 2 +- package.json | 3 ++- yarn.lock | 5 +++++ 14 files changed, 89 insertions(+), 42 deletions(-) create mode 100644 hydro/lib/readConfig.js diff --git a/.eslintrc.js b/.eslintrc.js index 4c1ee370c..f08ae2e9b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,5 +27,6 @@ module.exports = { "global-require": "off", "no-nested-ternary": "off", "no-multi-assign": "off", + "no-return-await": "off", }, }; diff --git a/hydro/entry/common.js b/hydro/entry/common.js index 0f80cd43b..d98b8795f 100644 --- a/hydro/entry/common.js +++ b/hydro/entry/common.js @@ -6,6 +6,7 @@ const builtinLib = [ 'axios', 'download', 'i18n', 'mail', 'markdown', 'md5', 'misc', 'paginate', 'hash.hydro', 'rank', 'template', 'validator', 'nav', 'sysinfo', 'testdata.convert.ini', + 'readConfig', ]; const builtinModel = [ diff --git a/hydro/handler/import.js b/hydro/handler/import.js index 474d82e5e..dd92b5271 100644 --- a/hydro/handler/import.js +++ b/hydro/handler/import.js @@ -1,3 +1,6 @@ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); const problem = require('../model/problem'); const { Route, Handler } = require('../service/server'); const { PERM_CREATE_PROBLEM } = require('../permission'); @@ -29,7 +32,14 @@ class ProblemImportHandler extends Handler { if (pid) pdoc.pid = pid; if (hidden) pdoc.hidden = true; const docId = await problem.add(domainId, pdoc.title, pdoc.content, this.user._id, pdoc); - await problem.setTestdata(docId, testdata); + const file = path.resolve(os.tmpdir(), 'hydro', `import_${domainId}_${pid}.zip`); + const w = fs.createWriteStream(file); + await new Promise((resolve, reject) => { + w.on('finish', resolve); + w.on('error', reject); + testdata.pipe(w); + }); + await problem.setTestdata(domainId, docId, file); this.response.body = { pid: pid || docId }; this.response.redirect = `/p/${pid || docId}/settings`; } diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 60589d63e..f3c402bdc 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -79,9 +79,15 @@ class ProblemHandler extends Handler { class ProblemCategoryHandler extends ProblemHandler { async get({ domainId, page = 1, category }) { this.response.template = 'problem_main.html'; - const q = { - $or: [{ category }, { tag: category }], - }; + const q = { $and: [] }; + for (const name of category) { + q.$and.push({ + $or: [ + { category: { $elemMatch: { $eq: name } } }, + { tag: { $elemMatch: { $eq: name } } }, + ], + }); + } let psdict = {}; if (!this.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false; const [pdocs, ppcount, pcount] = await paginate( @@ -97,10 +103,10 @@ class ProblemCategoryHandler extends ProblemHandler { const path = [ ['Hydro', 'homepage'], ['problem_main', 'problem_main'], - [category, null, true], + [category, null, null, true], ]; this.response.body = { - path, page, pcount, ppcount, pdocs, psdict, category, + path, page, pcount, ppcount, pdocs, psdict, category: category.join('+'), }; } } @@ -295,8 +301,7 @@ class ProblemDataUploadHandler extends ProblemManageHandler { async post({ domainId }) { if (!this.request.files.file) throw new BadRequestError(); - const r = fs.createReadStream(this.request.files.file.path); - await problem.setTestdata(domainId, this.pdoc.docId, r); + await problem.setTestdata(domainId, this.pdoc.docId, this.request.files.file.path); if (this.pdoc.data && typeof this.pdoc.data === 'object') { const f = await file.getMeta(this.pdoc.data); this.md5 = f.md5; diff --git a/hydro/lib/readConfig.js b/hydro/lib/readConfig.js new file mode 100644 index 000000000..2dcd95759 --- /dev/null +++ b/hydro/lib/readConfig.js @@ -0,0 +1,19 @@ +const Zip = require('adm-zip'); + +async function readConfig(filePath) { + 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 = module.exports = readConfig; diff --git a/hydro/loader.js b/hydro/loader.js index a02e0326e..96c8e2bcc 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -45,7 +45,7 @@ async function load() { process.stdin.setEncoding('utf8'); process.stdin.on('data', async (input) => { try { - const t = eval(input.toString().trim()); // eslint-disable-line no-eval + const t = eval(input.toString().trim()); if (t instanceof Promise) console.log(await t); else console.log(t); } catch (e) { diff --git a/hydro/model/contest.js b/hydro/model/contest.js index d4536be6e..bab43292e 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -2,7 +2,7 @@ const user = require('./user'); const problem = require('./problem'); const { ValidationError, ContestNotFoundError, ContestAlreadyAttendedError, - ContestNotAttendedError, ProblemNotFoundError, ContestScoreboardHiddenError, + ContestNotAttendedError, ContestScoreboardHiddenError, } = require('../error'); const { PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission'); const validator = require('../lib/validator'); @@ -74,7 +74,9 @@ const acm = { const rows = [columns]; for (const [rank, tsdoc] of rankedTsdocs) { const tsddict = {}; - if (tdoc.detail) { for (const item of tsdoc.journal) tsddict[item.pid] = item; } + if (tdoc.detail) { + for (const item of tsdoc.journal) tsddict[item.pid] = item; + } const row = []; row.push( { type: 'string', value: rank }, @@ -346,13 +348,7 @@ const ContestHandlerMixin = (c) => class extends c { } async verifyProblems(domainId, pids) { // eslint-disable-line class-methods-use-this - const r = []; - for (const pid of pids) { - const res = await problem.get(domainId, pid); // eslint-disable-line no-await-in-loop - if (res) r.push(res.docId); - else throw new ProblemNotFoundError(pid); - } - return r; + return await problem.getList(domainId, pids, true); } }; diff --git a/hydro/model/discussion.js b/hydro/model/discussion.js index 45315e434..c31d702cf 100644 --- a/hydro/model/discussion.js +++ b/hydro/model/discussion.js @@ -76,7 +76,7 @@ function editReply(domainId, drid, content) { async function delReply(domainId, drid) { const drdoc = await getReply(domainId, drid); if (!drdoc) throw new DocumentNotFoundError(drid); - return await Promise.all([ // eslint-disable-line no-return-await + return await Promise.all([ document.deleteOne(domainId, document.TYPE_DISCUSSION_REPLY, drid), document.inc(domainId, document.TYPE_DISCUSSION, drdoc.parentId, 'nReply', -1), ]); diff --git a/hydro/model/file.js b/hydro/model/file.js index 73e513a16..180fea72f 100644 --- a/hydro/model/file.js +++ b/hydro/model/file.js @@ -1,31 +1,36 @@ +const fs = require('fs'); const { ForbiddenError } = require('../error'); const db = require('../service/db'); -const fs = require('../service/gridfs'); +const gridfs = require('../service/gridfs'); const hash = require('../lib/hash.hydro'); const coll = db.collection('file'); +const collFile = db.collection('fs.files'); +const collChunk = db.collection('fs.chunks'); function _timestamp() { return Math.floor(Number(new Date()) / 1000000); } -async function add(stream, filename, meta = {}) { - const file = await coll.insertOne(meta); - const w = fs.openUploadStreamWithId(file.insertedId, filename); +async function add(streamOrPath, filename, meta = {}) { + if (typeof streamOrPath === 'string') streamOrPath = fs.createReadStream(streamOrPath); + const w = gridfs.openUploadStream(filename); + await coll.insertOne({ ...meta, _id: w.id }); await new Promise((resolve, reject) => { - stream.on('end', resolve); - stream.on('error', reject); - stream.pipe(w); + w.on('error', reject); + w.on('finish', resolve); + streamOrPath.pipe(w); }); - const c = await fs.find({ _id: file.insertedId }).toArray(); - await coll.updateOne({ _id: file.insertedId }, { $set: { md5: c[0].md5, size: c[0].size } }); - return file.insertedId; + const c = await gridfs.find({ _id: w.id }).toArray(); + await coll.updateOne({ _id: w.id }, { $set: { md5: c[0].md5, size: c[0].size } }); + return w.id; } function del(_id) { return Promise.all([ coll.deleteOne({ _id }), - fs.delete(_id), + collFile.deleteOne({ _id }), + collChunk.deleteMany({ files_id: _id }), ]); } @@ -58,7 +63,7 @@ async function get(_id, secret) { } } } - return fs.openDownloadStream(_id); + return gridfs.openDownloadStream(_id); } function getMeta(_id) { diff --git a/hydro/model/problem.js b/hydro/model/problem.js index a0257958a..dd6138837 100644 --- a/hydro/model/problem.js +++ b/hydro/model/problem.js @@ -3,6 +3,7 @@ const file = require('./file'); const document = require('./document'); const domain = require('./domain'); const { ProblemNotFoundError } = require('../error'); +const readConfig = require('../lib/readConfig'); /** * @typedef {import('../interface').Pdoc} Pdoc @@ -30,9 +31,13 @@ async function add(domainId, title, content, owner, { }) { const d = await domain.inc(domainId, 'pidCounter', 1); if (!pid) pid = d.pidCounter.toString(); - return await document.add(domainId, content, owner, document.TYPE_PROBLEM, d.pidCounter, null, null, { - pid, title, data, category, tag, hidden, nSubmit: 0, nAccept: 0, - }); + // eslint-disable-next-line no-return-await + return await document.add( + domainId, content, owner, document.TYPE_PROBLEM, d.pidCounter, null, null, + { + pid, title, data, category, tag, hidden, nSubmit: 0, nAccept: 0, + }, + ); } /** @@ -148,12 +153,12 @@ async function updateStatus(domainId, pid, uid, rid, status) { return true; } -async function setTestdata(domainId, _id, readStream) { - // TODO read config in zipfile +async function setTestdata(domainId, _id, filePath) { const pdoc = await get(domainId, _id); - const id = await file.add(readStream, 'data.zip'); - if (pdoc.data && typeof pdoc.data === 'object') file.dec(this.pdoc.data); - return await edit(domainId, _id, { data: id }); // eslint-disable-line no-return-await + const config = await readConfig(filePath); + const id = await file.add(filePath, 'data.zip'); + if (pdoc.data && typeof pdoc.data === 'object') file.dec(pdoc.data); + return await edit(domainId, _id, { data: id, config }); } global.Hydro.model.problem = module.exports = { diff --git a/hydro/model/setting.js b/hydro/model/setting.js index 3c7110c8d..f56a3e98f 100644 --- a/hydro/model/setting.js +++ b/hydro/model/setting.js @@ -1,7 +1,6 @@ const moment = require('moment-timezone'); const builtin = require('./builtin'); const options = require('../options'); -const i18n = require('../lib/i18n'); const countries = moment.tz.countries(); const tzs = new Set(); diff --git a/hydro/model/user.js b/hydro/model/user.js index b3b871782..924eabe61 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -112,7 +112,7 @@ async function changePassword(uid, currentPassword, newPassword) { const udoc = await getById(uid); udoc.checkPassword(currentPassword); const salt = String.random(); - return await coll.findOneAndUpdate({ // eslint-disable-line no-return-await + return await coll.findOneAndUpdate({ _id: udoc._id, }, { $set: { salt, hash: pwhash.hash(newPassword, salt), hashType: 'hydro' }, diff --git a/package.json b/package.json index b0e5d5631..0522d57f0 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "author": "masnn", "license": "GPL-3.0-only", "dependencies": { + "adm-zip": "^0.4.14", "axios": "^0.19.0", "bson": "^4.0.2", "js-yaml": "^3.13.1", @@ -49,4 +50,4 @@ ".build/module/**" ] } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 78b206293..56c052c14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -234,6 +234,11 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== +adm-zip@^0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.14.tgz#2cf312bcc9f8875df835b0f6040bd89be0a727a9" + integrity sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g== + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"