diff --git a/.gitignore b/.gitignore index 2635ab9b..93066a51 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ config.json .build/ *.build .uibuild/ +dist/ .cache/ *.cache __* diff --git a/hydro/handler/judge.js b/hydro/handler/judge.js index 178b7679..4cd39710 100644 --- a/hydro/handler/judge.js +++ b/hydro/handler/judge.js @@ -1,4 +1,6 @@ +const yaml = require('js-yaml'); const { PERM_JUDGE } = require('../permission'); +const { parseTimeMS, parseMemoryMB } = require('../utils'); const record = require('../model/record'); const problem = require('../model/problem'); const builtin = require('../model/builtin'); @@ -13,6 +15,7 @@ const { async function _postJudge(rdoc) { const accept = rdoc.status === builtin.STATUS_ACCEPTED; bus.publish('record_change', rdoc); + if (rdoc.type === 'run') return; const tasks = []; if (rdoc.tid) { tasks.push( @@ -26,7 +29,7 @@ async function _postJudge(rdoc) { if (accept && !rdoc.rejudged) { tasks.push( problem.inc(rdoc.domainId, rdoc.pid, 'nAccept', 1), - user.inc(rdoc.domainId, rdoc.uid, 'nAccept', 1), + user.incDomain(rdoc.domainId, rdoc.uid, 'nAccept', 1), ); } } @@ -78,6 +81,8 @@ async function end(body) { } if (body.status) $set.status = body.status; if (body.score) $set.score = body.score; + if (body.stdout) $set.stdout = body.stdout; + if (body.stderr) $set.stderr = body.stderr; if (body.time_ms) $set.time = body.time_ms; if (body.memory_kb) $set.memory = body.memory_kb; $set.judgeAt = new Date(); @@ -87,6 +92,34 @@ async function end(body) { rdoc = await record.update(body.domainId, body.rid, $set, $push); } +class PretestHandler extends Handler { + async post({ + domainId, pid, code, lang, time = '1s', memory = '256m', input = '', + }) { + if (pid) { + const pdoc = await problem.get(domainId, pid); + if (pdoc.config) { + const config = yaml.safeLoad(pdoc.config); + if (config.time) time = config.time; + if (config.memory) memory = config.memory; + } + } + const rid = await record.add(domainId, { + pid: pid || String.random(16), + uid: this.user._id, + type: 'run', + time: parseTimeMS(time), + memory: parseMemoryMB(memory), + input, + lang, + code, + hidden: true, + }); + this.response.body = { rid }; + this.response.redirect = this.url('record_detail', { rid }); + } +} + class JudgeHandler extends Handler { async get({ check = false }) { if (check) return; @@ -130,6 +163,7 @@ class JudgeConnectionHandler extends ConnectionHandler { } async function apply() { + Route('pretest', '/pretest', PretestHandler); Route('judge', '/judge', JudgeHandler, PERM_JUDGE); Connection('judge_conn', '/judge/conn', JudgeConnectionHandler, PERM_JUDGE); } diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index 803adfda..8811cad2 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -66,13 +66,13 @@ class SystemScriptHandler extends SystemHandler { async post({ domainId, id, args = '{}' }) { args = JSON.parse(args); - // TODO Do not use console.log const rid = await record.add(domainId, { pid: id, uid: this.user._id, lang: null, code: null, status: STATUS.STATUS_JUDGING, + hidden: true, }, false); async function report(data) { judge.next({ domainId, rid, ...data }); diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 0a2c7af9..5b3a002a 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -224,7 +224,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler { async prepare({ domainId, pid }) { this.pid = pid.toString(); this.domainId = domainId; - bus.subscribe(['record_change'], this.onRecordChange); + bus.subscribe(['record_change'], this, 'onRecordChange'); } async onRecordChange(data) { @@ -239,7 +239,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler { } async cleanup() { - bus.unsubscribe(['record_change'], this.onRecordChange); + bus.unsubscribe(['record_change'], this, 'onRecordChange'); } } diff --git a/hydro/handler/record.js b/hydro/handler/record.js index c8f3e3a1..c0c6bfc5 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -19,7 +19,7 @@ class RecordListHandler extends RecordHandler { domainId, page = 1, pid, tid, uidOrName, }) { this.response.template = 'record_main.html'; - const q = { tid }; + const q = { tid, hidden: false }; if (uidOrName) { q.$or = [ { unameLower: uidOrName.toLowerCase() }, @@ -64,6 +64,7 @@ class RecordDetailHandler extends RecordHandler { if (rdoc.uid !== this.user.uid && !this.user.hasPerm(PERM_READ_RECORD_CODE)) { rdoc.code = null; } + if (rdoc.type !== 'run') rdoc.stdout = rdoc.stderr = ''; const [pdoc, udoc] = await Promise.all([ problem.get(domainId, rdoc.pid, null, false), user.getById(domainId, rdoc.uid), @@ -76,7 +77,8 @@ class RecordDetailHandler extends RecordHandler { udoc, rdoc, pdoc, - show_status: true, + // TODO protect + showStatus: true, }; } } @@ -144,14 +146,14 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti return; } } - this.rid = rid; + this.rid = rid.toString(); bus.subscribe(['record_change'], this, 'onRecordChange'); this.onRecordChange({ value: rdoc }); } async onRecordChange(data) { const rdoc = data.value; - if (!rdoc._id.equals(this.rid)) return; + if (rdoc._id.toString() !== this.rid) return; this.send({ status_html: await this.renderHTML('record_detail_status.html', { rdoc }), summary_html: await this.renderHTML('record_detail_summary.html', { rdoc }), diff --git a/hydro/loader.js b/hydro/loader.js index a026bd9e..8c77d65f 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -59,7 +59,15 @@ async function executeCommand(input) { async function messageHandler(worker, msg) { if (!msg) msg = worker; if (msg.event) { - if (msg.event === 'stat') { + if (msg.event === 'bus') { + if (cluster.isMaster) { + for (const i in cluster.workers) { + if (i !== worker.id) cluster.workers[i].send(msg); + } + } else { + global.Hydro.service.bus.publish(msg.eventName, msg.payload, false); + } + } else if (msg.event === 'stat') { global.Hydro.stat.reqCount += msg.count; } else if (msg.event === 'restart') { console.log('Restarting'); diff --git a/hydro/model/record.js b/hydro/model/record.js index 7743d9fc..31283ce3 100644 --- a/hydro/model/record.js +++ b/hydro/model/record.js @@ -19,6 +19,7 @@ async function add(domainId, data, addTask = true) { score: 0, time: 0, memory: 0, + hidden: false, rejudged: false, judgeTexts: [], compilerTexts: [], @@ -31,15 +32,26 @@ async function add(domainId, data, addTask = true) { coll.insertOne(data), ]); if (addTask) { - await task.add({ - type: 'judge', + const t = { + type: data.type || 'judge', + event: data.type || 'judge', rid: res.insertedId, domainId, pid: data.pid, - data: (pdoc || {}).data, lang: data.lang, code: data.code, - }); + }; + if (t.type === 'judge') { + t.data = pdoc.data; + t.config = pdoc.config; + } else { + t.config = { + time: data.time, + memory: data.memory, + input: data.input, + }; + } + await task.add(t); } return res.insertedId; } @@ -66,8 +78,7 @@ async function update(domainId, rid, $set, $push, $unset) { if ($set && Object.keys($set).length) upd.$set = $set; if ($push && Object.keys($push).length) upd.$push = $push; if ($unset && Object.keys($unset).length) upd.$unset = $unset; - await coll.findOneAndUpdate({ domainId, _id }, upd); - const rdoc = await coll.findOne({ _id }); + const rdoc = await coll.findOneAndUpdate({ domainId, _id }, upd, { returnOriginal: false }); if (!rdoc) throw new RecordNotFoundError(rid); return rdoc; } diff --git a/hydro/model/task.js b/hydro/model/task.js index 79f3bb3f..b7e79c28 100644 --- a/hydro/model/task.js +++ b/hydro/model/task.js @@ -1,4 +1,3 @@ -const cluster = require('cluster'); const moment = require('moment-timezone'); const db = require('../service/db'); @@ -8,7 +7,6 @@ async function add(task) { const t = { ...task }; if (typeof t.executeAfter === 'object') t.executeAfter = t.executeAfter.getTime(); t.count = t.count || 1; - t.wait = t.wait || Object.keys(cluster.workers); t.executeAfter = t.executeAfter || new Date().getTime(); const res = await coll.insertOne(t); return res.insertedId; @@ -29,16 +27,9 @@ function del(_id) { async function getFirst(query) { const q = { ...query }; q.executeAfter = q.executeAfter || { $lt: new Date().getTime() }; - q.wait = { $elemMatch: { $eq: cluster.worker.id } }; const res = await coll.find(q).sort('_id', 1).limit(1).toArray(); if (res.length) { - if (res[0].count === 1) await coll.deleteOne({ _id: res[0]._id }); - else { - await coll.updateOne( - { _id: res[0]._id }, - { $inc: { count: -1 }, $pull: { wait: cluster.worker.id } }, - ); - } + await coll.deleteOne({ _id: res[0]._id }); if (res[0].interval) { await coll.insertOne({ ...res[0], executeAfter: moment().add(...res[0].interval).toDate(), diff --git a/hydro/model/user.js b/hydro/model/user.js index c110990e..b1df6da1 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -132,6 +132,7 @@ async function changePassword(uid, currentPassword, newPassword) { async function inc(_id, field, n = 1) { const udoc = await coll.findOne({ _id }); + if (!udoc) throw new UserNotFoundError(_id); udoc[field] = udoc[field] + n || n; await coll.updateOne({ _id }, { $set: { [field]: udoc[field] } }); return udoc; diff --git a/hydro/service/bus.js b/hydro/service/bus.js index ec476fc4..684b95de 100644 --- a/hydro/service/bus.js +++ b/hydro/service/bus.js @@ -1,5 +1,4 @@ const { EventEmitter } = require('events'); -const cluster = require('cluster'); const bus = new EventEmitter(); @@ -26,27 +25,18 @@ function unsubscribe(events, handler, funcName) { } } -function publish(event, data, isMaster = true) { - bus.emit(event, { value: data, event }); - if (isMaster && global.Hydro.model.task) { - global.Hydro.model.task.add({ - type: 'bus', - count: Object.keys(cluster.workers).length, - sender: cluster.worker.id, - event, - data, +function publish(event, payload, isMaster = true) { + if (isMaster && process.send) { + process.send({ + event: 'bus', + eventName: event, + payload, }); + } else { + bus.emit(event, { value: payload, event }); } } -function postInit() { - const { task } = global.Hydro.model; - task.consume({ type: 'bus' }, (data) => { - if (data.sender === cluster.worker.id) return; - publish(data.event, data.value, false); - }); -} - global.Hydro.service.bus = module.exports = { - subscribe, unsubscribe, publish, postInit, + subscribe, unsubscribe, publish, }; diff --git a/hydro/service/server.js b/hydro/service/server.js index a58c1660..ff39ebe1 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -297,7 +297,7 @@ class Handler { this.ctx.response.status = 302; this.ctx.redirect(this.response.redirect); } else { - if (this.response.body.redirect) { + if (this.response.redirect) { this.response.body = this.response.body || {}; this.response.body.url = this.response.redirect; } @@ -438,6 +438,7 @@ class ConnectionHandler { if (anchor) return `${res}#${anchor}`; } catch (e) { console.error(e.message); + console.error(name, kwargs); } return res; } diff --git a/hydro/utils.js b/hydro/utils.js index 02cb9b2d..2d0aac06 100644 --- a/hydro/utils.js +++ b/hydro/utils.js @@ -109,3 +109,24 @@ function folderSize(folderPath) { } exports.folderSize = folderSize; + +const TIME_RE = /^([0-9]+(?:\.[0-9]*)?)([mu]?)s?$/i; +const TIME_UNITS = { '': 1000, m: 1, u: 0.001 }; +const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i; +const MEMORY_UNITS = { k: 0.1, m: 1, g: 1024 }; + +function parseTimeMS(str) { + const match = TIME_RE.exec(str); + if (!match) throw new Error(str, 'error parsing time'); + return parseInt(parseFloat(match[1], 10) * TIME_UNITS[match[2]], 10); +} + +exports.parseTimeMS = parseTimeMS; + +function parseMemoryMB(str) { + const match = MEMORY_RE.exec(str); + if (!match) throw new Error(str, 'error parsing memory'); + return parseInt(parseFloat(match[1], 10) * MEMORY_UNITS[match[2]], 10); +} + +exports.parseMemoryMB = parseMemoryMB; diff --git a/module/migrate-vijos/hydro.json b/module/migrate-vijos/hydro.json index f52284a2..8f1fa9aa 100644 --- a/module/migrate-vijos/hydro.json +++ b/module/migrate-vijos/hydro.json @@ -1,5 +1,5 @@ { "id": "migrate-vijos", - "version": "1.0.1", + "version": "1.0.2", "description": "Migrate From Vijos" } \ No newline at end of file diff --git a/module/migrate-vijos/script.js b/module/migrate-vijos/script.js index c55669be..2b09f337 100644 --- a/module/migrate-vijos/script.js +++ b/module/migrate-vijos/script.js @@ -188,6 +188,7 @@ const tasks = { compilerTexts: doc.compiler_texts, rejudged: doc.rejudged, testCases, + hidden: doc.hidden, }; }, domain: { diff --git a/package.json b/package.json index 780d6f1f..4c026f50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.4.2", + "version": "2.4.3", "main": "hydro/loader.js", "repository": "https://github.com/hydro-dev/Hydro.git", "author": "masnn", @@ -64,4 +64,4 @@ ] }, "snyk": true -} +} \ No newline at end of file diff --git a/templates/record_detail.html b/templates/record_detail.html index da8613cf..553092c9 100644 --- a/templates/record_detail.html +++ b/templates/record_detail.html @@ -8,7 +8,7 @@
- {% if show_status %} + {% if showStatus %} {% include 'record_detail_status.html' %} {% endif %} {% if rdoc['code'] %} @@ -21,6 +21,23 @@
{% endif %} + {% if rdoc['stdout'] or rdoc['stderr'] %} +
+
+

{{ _('Stdout') }}

+
+ {% if rdoc['stdout'] %} +
+
{{ rdoc['stdout']|ansi|safe }}
+
+ {% endif %} + {% if rdoc['stderr'] %} +
+
{{ rdoc['stderr']|ansi|safe }}
+
+ {% endif %} +
+ {% endif %}
diff --git a/ui/pages/manage_script.page.js b/ui/pages/manage_script.page.js index 9f34b038..43370ebb 100644 --- a/ui/pages/manage_script.page.js +++ b/ui/pages/manage_script.page.js @@ -7,14 +7,6 @@ import request from 'vj/utils/request'; const page = new NamedPage('manage_script', () => { const runScriptDialog = new ActionDialog({ $body: $('.dialog__body--run-script > div'), - onDispatch(action) { - const $args = runScriptDialog.$dom.find('[name="args"]'); - if (action === 'ok' && $args.val() === '') { - $args.focus(); - return false; - } - return true; - }, }); runScriptDialog.clear = function () { this.$dom.find('[name="args"]').val('');