diff --git a/hydro/loader.js b/hydro/loader.js index 25f8c0353..6e630c3a8 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -1,4 +1,3 @@ -/* eslint-disable import/no-dynamic-require */ /* eslint-disable no-await-in-loop */ /* eslint-disable no-eval */ const fs = require('fs'); @@ -21,20 +20,35 @@ function ensureDir(dir) { const active = []; try { + // Let webpack pack builtin module together. // eslint-disable-next-line import/no-unresolved const f = require('../.build/builtin.json'); const m = { ...yaml.safeLoad(zlib.gunzipSync(f.data)), id: 'builtin' }; active.push(m); } catch (e) { - /* Ignore */ + // Builtin module is in the module directory +} + +const moduleRoots = [ + root('.build/module'), + root('module'), + root(path.resolve(os.homedir(), '.hydro', 'module')), + root('.'), +]; +let moduleRoot; +for (const i of moduleRoots) { + if (fs.existsSync(i) && fs.statSync(i).isDirectory()) { + moduleRoot = i; + break; + } } async function preload() { - const files = fs.readdirSync(root('.build/module')); + const files = fs.readdirSync(moduleRoot); for (const file of files) { if (file.endsWith('.hydro')) { try { - const f = fs.readFileSync(root(`.build/module/${file}`)); + const f = fs.readFileSync(root(`${moduleRoot}/${file}`)); const m = { ...yaml.safeLoad(zlib.gunzipSync(f)), id: file.split('.')[0] }; active.push(m); } catch (e) { @@ -61,6 +75,8 @@ async function preload() { } } } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Module Load Fail: ${i.id}`); console.error(e); } @@ -69,7 +85,7 @@ async function preload() { async function handler() { for (const i of active) { - if (i.handler) { + if (i.handler && !i.fail) { try { console.time(`Handler init: ${i.id}`); const exports = {}; @@ -78,6 +94,8 @@ async function handler() { eval(i.handler); console.timeEnd(`Handler init: ${i.id}`); } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Handler Load Fail: ${i.id}`); } } @@ -86,11 +104,13 @@ async function handler() { async function locale() { for (const i of active) { - if (i.locale) { + if (i.locale && !i.fail) { try { global.Hydro.lib.i18n(i.locale); console.log(`Locale init: ${i.id}`); } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Locale Load Fail: ${i.id}`); } } @@ -99,11 +119,13 @@ async function locale() { async function template() { for (const i of active) { - if (i.template) { + if (i.template && !i.fail) { try { Object.assign(global.Hydro.template, i.template); console.log(`Template init: ${i.id}`); } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Template Load Fail: ${i.id}`); } } @@ -112,7 +134,7 @@ async function template() { async function model() { for (const i of active) { - if (i.model) { + if (i.model && !i.fail) { try { console.time(`Model init: ${i.id}`); const exports = {}; @@ -121,6 +143,8 @@ async function model() { if ((module.exports || {}).index) await module.exports.index(); console.timeEnd(`Model init: ${i.id}`); } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Model Load Fail: ${i.id}`); } } @@ -129,7 +153,7 @@ async function model() { async function lib() { for (const i of active) { - if (i.lib) { + if (i.lib && !i.fail) { try { console.time(`Lib init: ${i.id}`); const exports = {}; @@ -138,6 +162,8 @@ async function lib() { eval(i.lib); console.timeEnd(`Lib init: ${i.id}`); } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Lib Load Fail: ${i.id}`); } } @@ -146,7 +172,7 @@ async function lib() { async function service() { for (const i of active) { - if (i.service) { + if (i.service && !i.fail) { try { console.time(`Service init: ${i.id}`); const exports = {}; // eslint-disable-line no-unused-vars @@ -155,6 +181,8 @@ async function service() { if ((module.exports || {}).init) await module.exports.init(); console.timeEnd(`Service init: ${i.id}`); } catch (e) { + i.fail = true; + fail.push(i.id); console.error(`Service Load Fail: ${i.id}`); } } diff --git a/hydro/options.js b/hydro/options.js index 792111058..d704d3887 100644 --- a/hydro/options.js +++ b/hydro/options.js @@ -1,7 +1,6 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); -const yaml = require('js-yaml'); const { defaultsDeep } = require('lodash'); let options = { diff --git a/hydro/service/setup.js b/hydro/service/setup.js index 28e78160b..4b1f131d4 100644 --- a/hydro/service/setup.js +++ b/hydro/service/setup.js @@ -1,21 +1,23 @@ const fs = require('fs'); const path = require('path'); +const http = require('http'); const Koa = require('koa'); const morgan = require('koa-morgan'); const Body = require('koa-body'); const Router = require('koa-router'); const cache = require('koa-static-cache'); -const http = require('http'); const nunjucks = require('nunjucks'); +const mongodb = require('mongodb'); -function Loader() { } -Loader.prototype.getSource = function getSource(name) { - if (!global.Hydro.template[name]) throw new Error(`Cannot get template ${name}`); - return { - src: global.Hydro.template[name], - path: name, - }; -}; +class Loader { + getSource(name) { // eslint-disable-line class-methods-use-this + if (!global.Hydro.template[name]) throw new Error(`Cannot get template ${name}`); + return { + src: global.Hydro.template[name], + path: name, + }; + } +} class Nunjucks extends nunjucks.Environment { constructor() { @@ -38,36 +40,55 @@ function render(name) { }); }); } -const app = new Koa(); -const server = http.createServer(app.callback()); -const router = new Router(); -app.keys = ['Hydro']; -app.use(cache(path.join(process.cwd(), '.uibuild'), { - maxAge: 365 * 24 * 60 * 60, -})); -app.use(Body({ - multipart: true, - formidable: { - maxFileSize: 256 * 1024 * 1024, - }, -})); - -router.get('/', async (ctx) => { - ctx.body = await render('setup.html'); - ctx.response.type = 'text/html'; -}); -router.post('/', async (ctx) => { - const { - host, port, name, username, password, - } = ctx.request.body; - fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({ - host, port, name, username, password, +async function setup() { + const app = new Koa(); + const server = http.createServer(app.callback()); + const router = new Router(); + app.keys = ['Hydro']; + app.use(cache(path.join(process.cwd(), '.uibuild'), { + maxAge: 365 * 24 * 60 * 60, })); - ctx.body = await render('setup_done.html'); - ctx.response.type = 'text/html'; -}); + app.use(Body({ formidable: { maxFileSize: 256 * 1024 * 1024 } })); + router.get('/', async (ctx) => { + ctx.body = await render('setup.html'); + ctx.response.type = 'text/html'; + }); + const p = new Promise((resolve) => { + router.post('/', async (ctx) => { + const { + host, port, name, username, password, + } = ctx.request.body; + let mongourl = 'mongodb://'; + if (username) mongourl += `${username}:${password}@`; + mongourl += `${host}:${port}/${name}`; + try { + const Database = await mongodb.MongoClient.connect(mongourl, { + useNewUrlParser: true, useUnifiedTopology: true, + }); + const db = Database.db(name); + // Check Write Access + await db.collection('test').insertOne({ test: 'test' }); + fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({ + host, port, name, username, password, + })); + ctx.redirect('/'); + resolve(); + } catch (e) { + ctx.body = `Error connecting to database: ${e.message}\n${e.stack}`; + } + }); + }); + app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); + app.use(router.routes()).use(router.allowedMethods()); + server.listen(8888); + console.log('Server listening at: 8888'); + await p; + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) reject(err); + else resolve(); + }); + }); +} -app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); -app.use(router.routes()).use(router.allowedMethods()); -server.listen(8888); -console.log('Server listening at: 8888'); +module.exports = { setup }; diff --git a/module/judger/build.js b/module/judger/build.js index 55d8d0f5c..ca601653b 100644 --- a/module/judger/build.js +++ b/module/judger/build.js @@ -1,10 +1,14 @@ const fs = require('fs'); const path = require('path'); const child = require('child_process'); +const yaml = require('js-yaml'); exports.prebuild = async function prebuild() { if (!fs.existsSync(path.resolve(__dirname, 'HydroJudger'))) { const res = child.spawnSync('git clone https://github.com/hydro-dev/HydroJudger.git'); if (res.error) throw res.error; } + let lang = fs.readFileSync(path.resolve(__dirname, 'langs.yaml')).toString(); + lang = yaml.safeLoad(lang); + fs.writeFileSync(path.resolve(__dirname, '__lang.json'), JSON.stringify(lang)); }; diff --git a/module/judger/service.js b/module/judger/service.js index d1e40d60c..3f4f202d6 100644 --- a/module/judger/service.js +++ b/module/judger/service.js @@ -3,9 +3,11 @@ const path = require('path'); const child = require('child_process'); async function postInit() { + const config = require('./HydroJudger/judger/config'); + // eslint-disable-next-line import/no-unresolved + config.LANGS = require('./__langs.json'); const { mkdirp, rmdir, compilerText } = require('./HydroJudger/judger/utils'); const log = require('./HydroJudger/judger/log'); - const { CACHE_DIR, TEMP_DIR } = require('./HydroJudger/judger/config'); const tmpfs = require('./HydroJudger/judger/tmpfs'); const { FormatError, CompileError, SystemError } = require('./HydroJudger/judger/error'); const { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } = require('./HydroJudger/judger/status'); @@ -17,7 +19,7 @@ async function postInit() { const { judge } = global.Hydro.handler; async function problemData(pid, savePath) { - const tmpFilePath = path.resolve(CACHE_DIR, `download_${pid}`); + const tmpFilePath = path.resolve(config.CACHE_DIR, `download_${pid}`); const res = await problem.getData(pid); const w = await fs.createWriteStream(tmpFilePath); res.data.pipe(w); @@ -38,7 +40,7 @@ async function postInit() { } async function cacheOpen(pid, version) { - const filePath = path.join(CACHE_DIR, pid); + const filePath = path.join(config.CACHE_DIR, pid); if (fs.existsSync(filePath)) { let ver; try { @@ -112,7 +114,7 @@ async function postInit() { this.data = this.request.data; this.next = getNext(this); this.end = getEnd(this.rid); - this.tmpdir = path.resolve(TEMP_DIR, 'tmp', this.rid); + this.tmpdir = path.resolve(config.TEMP_DIR, 'tmp', this.rid); this.clean = []; mkdirp(this.tmpdir); tmpfs.mount(this.tmpdir, '64m'); @@ -138,6 +140,7 @@ async function postInit() { }); } } + // eslint-disable-next-line no-await-in-loop for (const clean of this.clean) await clean().catch(); tmpfs.umount(this.tmpdir); await rmdir(this.tmpdir);