2019-03-26 14:27:30 +01:00
|
|
|
// Requires
|
2019-03-20 16:52:01 +01:00
|
|
|
const fs = require('fs');
|
2019-03-22 10:56:49 +01:00
|
|
|
const process = require('process');
|
|
|
|
const pathtools = require('path');
|
2019-03-20 16:52:01 +01:00
|
|
|
const express = require('express');
|
2019-03-26 15:07:36 +01:00
|
|
|
const fileUpload = require('express-fileupload');
|
2019-03-22 10:56:49 +01:00
|
|
|
const { spawn } = require('child_process');
|
2019-03-20 16:52:01 +01:00
|
|
|
const pug = require('pug');
|
2019-03-22 10:56:49 +01:00
|
|
|
const unzip = require('unzip');
|
2019-03-26 15:07:36 +01:00
|
|
|
const touch = require('touch');
|
2019-03-27 18:33:41 +01:00
|
|
|
const bcrypt = require('bcryptjs');
|
2019-03-26 14:27:30 +01:00
|
|
|
|
|
|
|
// Consts
|
2019-03-20 16:52:01 +01:00
|
|
|
const port = 8000;
|
2019-03-27 18:33:41 +01:00
|
|
|
const pythonPath = '/usr/bin/python3'
|
2019-03-26 15:07:36 +01:00
|
|
|
const aisPath = 'pytron_run/ai_manager/'
|
|
|
|
const aisPathOld = 'pytron_run/ai_manager_old'
|
2019-03-27 18:33:41 +01:00
|
|
|
const hashPath = "__bcrypt__hash.txt"
|
2019-03-22 10:56:49 +01:00
|
|
|
|
2019-03-22 10:57:49 +01:00
|
|
|
// Create the directories to store the files
|
2019-03-26 14:27:30 +01:00
|
|
|
try { fs.mkdirSync(aisPath); } catch { }
|
|
|
|
try { fs.mkdirSync(aisPathOld); } catch { }
|
2019-03-22 10:57:49 +01:00
|
|
|
|
2019-03-22 10:56:49 +01:00
|
|
|
let pythonRunning = false;
|
|
|
|
let pythonShouldRun = false;
|
|
|
|
|
|
|
|
function runPython() {
|
|
|
|
process.stdout.write('Requested to run python');
|
|
|
|
if (pythonRunning) {
|
|
|
|
process.stdout.write(' buy python is already running');
|
|
|
|
pythonShouldRun = true;
|
|
|
|
} else {
|
|
|
|
pythonRunning = true;
|
|
|
|
}
|
|
|
|
process.stdout.write('\n');
|
|
|
|
|
2019-03-26 15:39:43 +01:00
|
|
|
let p = spawn(pythonPath, ['run.py'], {cwd: 'pytron_run'});
|
2019-03-22 10:56:49 +01:00
|
|
|
p.stdout.on('data', (data) => {
|
|
|
|
let content = data.toString().split('\n');
|
|
|
|
for (let line of content) {
|
2019-03-26 15:07:36 +01:00
|
|
|
console.log('run.py: ' + line);
|
2019-03-22 10:56:49 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
p.stderr.on('data', (data) => {
|
|
|
|
let content = data.toString().split('\n');
|
|
|
|
for (let line of content) {
|
2019-03-26 15:07:36 +01:00
|
|
|
console.log('run.py: ' + line);
|
2019-03-22 10:56:49 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
p.on('close', (code) => {
|
2019-03-26 15:07:36 +01:00
|
|
|
process.stdout.write('Python finished executing');
|
2019-03-22 10:56:49 +01:00
|
|
|
if (pythonShouldRun) {
|
|
|
|
process.stdout.write(' but another request arrive, so we will relaunch it\n');
|
|
|
|
pythonShouldRun = false;
|
2019-03-27 17:06:10 +01:00
|
|
|
pythonRunning = false;
|
2019-03-22 10:56:49 +01:00
|
|
|
runPython();
|
|
|
|
} else {
|
|
|
|
process.stdout.write('\n');
|
|
|
|
pythonRunning = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-03-20 16:52:01 +01:00
|
|
|
|
2019-03-20 17:13:06 +01:00
|
|
|
function parse(data) {
|
|
|
|
let content = JSON.parse(data);
|
|
|
|
let parsed = {ais: [], battles: {}};
|
|
|
|
for (let ai of content.ais) {
|
|
|
|
parsed.ais.push({
|
|
|
|
name: ai,
|
|
|
|
victories: 0,
|
|
|
|
defeats: 0,
|
2019-03-26 12:24:36 +01:00
|
|
|
nulls: 0,
|
|
|
|
score: 0,
|
2019-03-20 17:13:06 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let key in content.battles) {
|
|
|
|
let battlers = key.split('/');
|
|
|
|
let ai1 = battlers[0];
|
|
|
|
let ai2 = battlers[1];
|
2019-03-26 12:24:36 +01:00
|
|
|
parsed.battles[ai1 + '/' + ai2] = content.battles[key][0];
|
|
|
|
parsed.battles[ai2 + '/' + ai1] = content.battles[key][1];
|
|
|
|
|
|
|
|
let realAi1 = parsed.ais.find((x) => x.name == ai1);
|
|
|
|
let realAi2 = parsed.ais.find((x) => x.name == ai2);
|
|
|
|
realAi1.victories += content.battles[key][0];
|
|
|
|
realAi1.defeats += content.battles[key][1];
|
|
|
|
realAi2.victories += content.battles[key][1];
|
|
|
|
realAi2.defeats += content.battles[key][0];
|
|
|
|
realAi1.nulls += content.battles[key][2];
|
|
|
|
realAi2.nulls += content.battles[key][2];
|
2019-03-20 17:13:06 +01:00
|
|
|
}
|
2019-03-26 12:24:36 +01:00
|
|
|
|
2019-03-20 17:13:06 +01:00
|
|
|
parsed.sortedAis = parsed.ais.slice(0);
|
|
|
|
|
2019-03-26 12:24:36 +01:00
|
|
|
for (let ai of parsed.sortedAis) {
|
|
|
|
ai.score = ai.victories * 3 + ai.nulls;
|
|
|
|
}
|
|
|
|
|
|
|
|
parsed.sortedAis.sort((a, b) => b.score - a.score);
|
2019-03-27 18:33:41 +01:00
|
|
|
if (parsed.sortedAis.length > 0) parsed.sortedAis[0].rank = 1;
|
|
|
|
if (parsed.sortedAis.length > 1) parsed.sortedAis[1].rank = 2;
|
|
|
|
if (parsed.sortedAis.length > 2) parsed.sortedAis[2].rank = 3;
|
2019-03-20 17:13:06 +01:00
|
|
|
|
|
|
|
return parsed;
|
|
|
|
}
|
|
|
|
|
2019-03-27 18:33:41 +01:00
|
|
|
function saveArchiveAndRun(req, res) {
|
|
|
|
|
|
|
|
let path = pathtools.join(aisPath, req.body.name);
|
|
|
|
try { fs.mkdirSync(path); } catch { }
|
|
|
|
|
|
|
|
let zipfile = pathtools.join(path, 'archive.zip');
|
|
|
|
req.files.archive.mv(zipfile, (err) => {
|
|
|
|
if (err != null) {
|
|
|
|
console.log(err);
|
|
|
|
res.render('error', {message: 'Unable to save the ZIP archive to the server'});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fs.createReadStream(zipfile)
|
|
|
|
.pipe(unzip.Extract({path}))
|
|
|
|
.on('close', () => {
|
|
|
|
// Touch __init__.py
|
|
|
|
touch(pathtools.join(path, '__init__.py'), () => {
|
|
|
|
// Trigger python_run
|
|
|
|
runPython();
|
|
|
|
res.redirect('/');
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.on('error', () => {
|
|
|
|
res.render('error', {message: 'Failed to unzip the archive'});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-03-26 14:27:30 +01:00
|
|
|
function startServer() {
|
|
|
|
const app = express();
|
|
|
|
app.set('view engine', 'pug');
|
|
|
|
app.use(fileUpload());
|
2019-03-20 16:52:01 +01:00
|
|
|
|
2019-03-26 14:27:30 +01:00
|
|
|
app.get('/', (req, res) => {
|
2019-03-26 15:39:43 +01:00
|
|
|
fs.readFile('pytron_run/assets/data.json', 'utf-8', (err, data) => {
|
2019-03-26 14:27:30 +01:00
|
|
|
if (err != null) {
|
|
|
|
console.log(err);
|
|
|
|
}
|
|
|
|
let parsed = parse(data);
|
2019-03-26 15:07:36 +01:00
|
|
|
res.render('index', {
|
|
|
|
data: parsed,
|
|
|
|
running: pythonRunning,
|
|
|
|
});
|
2019-03-26 14:27:30 +01:00
|
|
|
});
|
2019-03-20 16:52:01 +01:00
|
|
|
});
|
2019-03-26 14:27:30 +01:00
|
|
|
|
|
|
|
app.get('/upload', (req, res) => {
|
|
|
|
res.render('upload', {});
|
|
|
|
});
|
|
|
|
|
|
|
|
app.post('/upload-target', (req, res) => {
|
2019-03-27 16:02:53 +01:00
|
|
|
|
|
|
|
if (!req.body.name) {
|
|
|
|
res.render('error', {message: "You have to enter a name in the form"});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-27 18:33:41 +01:00
|
|
|
if (!req.body.password) {
|
|
|
|
res.render('error', {message: "You have to enter a password in the form"});
|
|
|
|
}
|
|
|
|
|
2019-03-27 16:02:53 +01:00
|
|
|
if (req.body.name.indexOf('.') !== -1) {
|
|
|
|
res.render('error', {message: "The name of your AI can't contain dots"});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!req.files.archive) {
|
|
|
|
res.render('error', {message: "You have to send a ZIP archive"});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-27 18:33:41 +01:00
|
|
|
let path = pathtools.join(aisPath, req.body.name);
|
|
|
|
let aiExisted;
|
2019-03-26 14:27:30 +01:00
|
|
|
try {
|
2019-03-27 18:33:41 +01:00
|
|
|
fs.statSync(path).isDirectory();
|
|
|
|
aiExisted = true;
|
|
|
|
} catch (e) {
|
|
|
|
aiExisted = false;
|
|
|
|
fs.mkdirSync(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (aiExisted) {
|
|
|
|
fs.readFile(pathtools.join(path, hashPath), 'utf-8', function(err, data) {
|
|
|
|
|
|
|
|
if (err != null) {
|
|
|
|
res.render('error', {message: "Couldn't read hashed password"});
|
|
|
|
console.log(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the AI already existed, verify the password
|
|
|
|
bcrypt.compare(req.body.password, data, function(err, success) {
|
|
|
|
if (err != null) {
|
|
|
|
res.render('error', {message: "Couldn't compare password"});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
res.render('error', {message: "Authentication failed"});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-26 14:27:30 +01:00
|
|
|
try {
|
2019-03-27 18:33:41 +01:00
|
|
|
if (fs.statSync(path).isDirectory()) {
|
|
|
|
// Move it to old
|
|
|
|
let version = 0;
|
|
|
|
for(;;) {
|
|
|
|
let movePath = pathtools.join(aisPathOld, req.body.name + '.' + version);
|
|
|
|
try {
|
|
|
|
fs.statSync(movePath);
|
|
|
|
// If the sync succeded, it means that the directory already exists
|
|
|
|
version++;
|
|
|
|
} catch {
|
|
|
|
// If we're here, it means that we found the right path. We
|
|
|
|
// will move the old one to the old directories, and save
|
|
|
|
// the new one
|
|
|
|
fs.renameSync(path, movePath);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-26 14:27:30 +01:00
|
|
|
} catch {
|
2019-03-27 18:33:41 +01:00
|
|
|
// Nothing to do here
|
2019-03-26 14:27:30 +01:00
|
|
|
}
|
2019-03-27 18:33:41 +01:00
|
|
|
|
|
|
|
// Save archive and run stuff
|
|
|
|
saveArchiveAndRun(req, res);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
bcrypt.hash(req.body.password, 10, function(err, hash) {
|
|
|
|
|
|
|
|
if (err != null) {
|
|
|
|
res.render('error', {message: "Couldn't hash password"});
|
|
|
|
return;
|
2019-03-22 10:56:49 +01:00
|
|
|
}
|
|
|
|
|
2019-03-27 18:33:41 +01:00
|
|
|
// Store hash in your password DB.
|
|
|
|
fs.writeFile(pathtools.join(path, hashPath), hash, (err) => {
|
|
|
|
|
|
|
|
if (err != null) {
|
|
|
|
res.render('error', {message: "Couldn't save hashed password"});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save archive and run stuff
|
|
|
|
saveArchiveAndRun(req, res);
|
2019-03-26 14:27:30 +01:00
|
|
|
|
|
|
|
});
|
2019-03-27 18:33:41 +01:00
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-03-22 10:56:49 +01:00
|
|
|
});
|
|
|
|
|
2019-03-26 14:27:30 +01:00
|
|
|
app.use('/static', express.static('static'));
|
|
|
|
|
|
|
|
app.listen(port, () => {
|
|
|
|
console.log(`Server listening on port ${port}!`)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function main() {
|
|
|
|
switch (process.argv[2]) {
|
|
|
|
case 'start':
|
|
|
|
startServer();
|
|
|
|
break;
|
|
|
|
case 'python':
|
|
|
|
runPython();
|
|
|
|
break;
|
|
|
|
default:
|
2019-03-26 15:07:36 +01:00
|
|
|
console.log('Unknown option: ' + process.argv[2]);
|
|
|
|
console.log('Usage: node server.js start|python')
|
2019-03-26 14:27:30 +01:00
|
|
|
process.exit(1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-03-20 16:52:01 +01:00
|
|
|
|
2019-03-26 14:27:30 +01:00
|
|
|
main();
|