Skip to content

Commit 63db7c4

Browse files
committed
Support C++ and detach algorithms and tracers submodules
1 parent 3e5a2f0 commit 63db7c4

File tree

12 files changed

+119
-80
lines changed

12 files changed

+119
-80
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
/build
44
/npm-debug.log
55
/package-lock.json
6+
/src/backend/public
67
.DS_Store

.gitmodules

Lines changed: 0 additions & 3 deletions
This file was deleted.

app/backend.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ if (__DEV__) {
1515

1616
let lastHash = null;
1717
let httpServer = null;
18-
compiler.watch({}, (err, stats) => {
18+
compiler.watch({
19+
watchOptions: {
20+
ignored: /public/,
21+
},
22+
}, (err, stats) => {
1923
if (err) {
2024
lastHash = null;
2125
compiler.purgeInputFileSystem();
@@ -25,7 +29,7 @@ if (__DEV__) {
2529
lastHash = stats.hash;
2630
console.info(stats.toString({
2731
cached: false,
28-
colors: true
32+
colors: true,
2933
}));
3034

3135
try {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "2.0.0",
44
"description": "Algorithm Visualizer",
55
"scripts": {
6-
"postinstall": "docker pull baekjoon/onlinejudge-java:1.8",
6+
"postinstall": "docker pull openjdk:8 && docker pull gcc:8.1",
77
"dev": "NODE_ENV=development node bin/www",
88
"start": "NODE_ENV=production node bin/www",
99
"build": "npm run build:frontend && npm run build:backend",

src/backend/common/util.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,16 @@
1-
export {};
1+
import Promise from 'bluebird';
2+
import child_process from 'child_process';
3+
4+
const execute = (command, cwd, { stdout = process.stdout, stderr = process.stderr } = {}) => new Promise((resolve, reject) => {
5+
if (!cwd) return reject(new Error('CWD Not Specified'));
6+
const child = child_process.exec(command, { cwd }, (error, stdout, stderr) => {
7+
if (error) return reject(new Error(stderr));
8+
resolve(stdout);
9+
});
10+
if (stdout) child.stdout.pipe(stdout);
11+
if (stderr) child.stderr.pipe(stderr);
12+
});
13+
14+
export {
15+
execute,
16+
};

src/backend/controllers/categories.js

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import express from 'express';
2-
import fs from 'fs';
2+
import Promise from 'bluebird';
3+
import fs from 'fs-extra';
34
import path from 'path';
45
import { NotFoundError } from '/common/error';
5-
import { exec } from 'child_process';
66
import { GitHubApi } from '/apis';
7+
import { execute } from '/common/util';
78

89
const router = express.Router();
910

10-
const getPath = (...args) => path.resolve(__dirname, '..', 'public', 'algorithms', ...args);
11-
const createKey = name => name.toLowerCase().replace(/ /g, '-');
12-
const list = dirPath => fs.readdirSync(dirPath).filter(fileName => !fileName.startsWith('.'));
11+
const repoPath = path.resolve(__dirname, '..', 'public', 'algorithms');
12+
const getPath = (...args) => path.resolve(repoPath, ...args);
1313

1414
const cacheCategories = () => {
1515
const allFiles = [];
16+
17+
const createKey = name => name.toLowerCase().replace(/ /g, '-');
18+
const list = dirPath => fs.readdirSync(dirPath).filter(fileName => !fileName.startsWith('.'));
19+
1620
const cacheCategory = categoryName => {
1721
const categoryKey = createKey(categoryName);
1822
const categoryPath = getPath(categoryName);
@@ -64,54 +68,53 @@ const cacheCategories = () => {
6468
return cacheCommitAuthors(page + 1, commitAuthors);
6569
}
6670
});
67-
const cacheContributors = (commitAuthors, fileIndex = 0) => {
68-
const file = allFiles[fileIndex];
69-
if (file) {
70-
const cwd = getPath();
71-
exec(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, { cwd }, (error, stdout, stderr) => {
72-
if (!error && !stderr) {
73-
const output = stdout.toString().replace(/\n$/, '');
74-
const shas = output.split('\n').reverse();
75-
const contributors = [];
76-
for (const sha of shas) {
77-
const author = commitAuthors[sha];
78-
if (author && !contributors.find(contributor => contributor.login === author.login)) {
79-
contributors.push(author);
80-
}
71+
const cacheContributors = commitAuthors => Promise.each(allFiles, file => {
72+
return execute(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, getPath(), { stdout: null })
73+
.then(stdout => {
74+
const output = stdout.toString().replace(/\n$/, '');
75+
const shas = output.split('\n').reverse();
76+
const contributors = [];
77+
for (const sha of shas) {
78+
const author = commitAuthors[sha];
79+
if (author && !contributors.find(contributor => contributor.login === author.login)) {
80+
contributors.push(author);
8181
}
82-
file.contributors = contributors;
8382
}
84-
cacheContributors(commitAuthors, fileIndex + 1);
83+
file.contributors = contributors;
8584
});
86-
}
87-
};
85+
});
8886
cacheCommitAuthors().then(cacheContributors);
8987

9088
return categories;
9189
};
92-
const cachedCategories = cacheCategories(); // TODO: cache again when webhooked
93-
94-
const getCategories = (req, res, next) => {
95-
res.json({ categories: cachedCategories });
96-
};
9790

98-
const getAlgorithm = (req, res, next) => {
99-
const { categoryKey, algorithmKey } = req.params;
91+
const downloadCategories = () => (
92+
fs.pathExistsSync(repoPath) ?
93+
execute(`git fetch && git reset --hard origin/master`, repoPath) :
94+
execute(`git clone [email protected]:algorithm-visualizer/algorithms ${repoPath}`, __dirname)
95+
);
10096

101-
const category = cachedCategories.find(category => category.key === categoryKey);
102-
if (!category) return next(new NotFoundError());
103-
const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
104-
if (!algorithm) return next(new NotFoundError());
97+
let categories = []; // TODO: download again when webhooked
98+
downloadCategories().then(() => categories = cacheCategories());
10599

106-
const titles = [category.name, algorithm.name];
107-
const files = algorithm.files.map(({ name, content, contributors }) => ({ name, content, contributors }));
108-
res.json({ algorithm: { titles, files } });
109-
};
110100

111101
router.route('/')
112-
.get(getCategories);
102+
.get((req, res, next) => {
103+
res.json({ categories });
104+
});
113105

114106
router.route('/:categoryKey/:algorithmKey')
115-
.get(getAlgorithm);
107+
.get((req, res, next) => {
108+
const { categoryKey, algorithmKey } = req.params;
109+
110+
const category = categories.find(category => category.key === categoryKey);
111+
if (!category) return next(new NotFoundError());
112+
const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
113+
if (!algorithm) return next(new NotFoundError());
114+
115+
const titles = [category.name, algorithm.name];
116+
const files = algorithm.files.map(({ name, content, contributors }) => ({ name, content, contributors }));
117+
res.json({ algorithm: { titles, files } });
118+
});
116119

117120
export default router;

src/backend/controllers/tracers.js

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,79 @@
1-
import Promise from 'bluebird';
21
import express from 'express';
32
import fs from 'fs-extra';
43
import uuid from 'uuid';
54
import path from 'path';
6-
import child_process from 'child_process';
75
import { GitHubApi } from '/apis';
8-
import { __PROD__ } from '/environment';
6+
import { execute } from '/common/util';
97
import { CompileError, RuntimeError } from '/common/error';
108

119
const router = express.Router();
1210

13-
const getLibPath = (...args) => path.resolve(__dirname, '..', 'public', 'libs', ...args);
11+
const repoPath = path.resolve(__dirname, '..', 'public', 'tracers');
12+
const getLibsPath = (...args) => path.resolve(repoPath, 'libs', ...args);
13+
const getCodesPath = (...args) => path.resolve(__dirname, '..', 'public', 'codes', ...args);
1414

15-
const downloadLibs = () => {
16-
GitHubApi.getLatestRelease('algorithm-visualizer', 'tracers').then(release => {
17-
return Promise.each(release.assets, asset => GitHubApi.download(asset.browser_download_url, getLibPath(asset.name)));
18-
});
19-
};
20-
if (__PROD__) downloadLibs(); // TODO: download again when webhooked
15+
const buildRelease = release => (
16+
fs.pathExistsSync(repoPath) ?
17+
execute(`git fetch && ! git diff-index --quiet ${release.target_commitish}`, repoPath) :
18+
execute(`git clone [email protected]:algorithm-visualizer/tracers ${repoPath}`, __dirname)
19+
).then(() => execute(`git reset --hard ${release.target_commitish} && npm install && npm run build`, repoPath));
20+
21+
GitHubApi.getLatestRelease('algorithm-visualizer', 'tracers').then(buildRelease);
22+
// TODO: build release when webhooked
2123

2224
const getJsWorker = (req, res, next) => {
23-
res.sendFile(getLibPath('js.js'));
25+
res.sendFile(getLibsPath('js', 'tracers.js'));
2426
};
2527

26-
const execute = (imageId, srcPath, command, ErrorClass) => new Promise((resolve, reject) => {
27-
const libPath = getLibPath();
28+
const executeInDocker = (imageId, ext, srcPath, command) => {
2829
// TODO: memory limit + time limit + space limit?
29-
const dockerCommand = `docker run --rm -w=/usr/judge -t -v=${libPath}:/usr/bin/tracers:ro -v=${srcPath}:/usr/judge:rw -e MAX_TRACES=1000000 -e MAX_TRACERS=100 ${imageId}`;
30-
child_process.exec(`${dockerCommand} ${command}`, (error, stdout, stderr) => {
31-
if (error) return reject(new ErrorClass(stdout));
32-
resolve();
33-
});
34-
});
35-
36-
const trace = ({ imageId, compileCommand, runCommand }) => (req, res, next) => {
30+
const libsPath = getLibsPath(ext);
31+
const dockerCommand = [
32+
`docker run --rm`,
33+
'-w=/usr/judge',
34+
`-v=${libsPath}:/usr/bin/tracers:ro`,
35+
`-v=${srcPath}:/usr/judge:rw`,
36+
`-e MAX_TRACES=${1e6} -e MAX_TRACERS=${1e2}`,
37+
imageId,
38+
].join(' ');
39+
return execute(`${dockerCommand} ${command}`, repoPath, { stdout: null, stderr: null });
40+
};
41+
42+
const trace = ({ ext, imageId, compileCommand, runCommand }) => (req, res, next) => {
3743
const { code } = req.body;
38-
const srcPath = path.resolve(__dirname, '..', 'public', 'codes', uuid.v4());
39-
fs.outputFile(path.resolve(srcPath, 'code.java'), code)
40-
.then(() => execute(imageId, srcPath, compileCommand, CompileError))
41-
.then(() => execute(imageId, srcPath, runCommand, RuntimeError))
44+
const srcPath = getCodesPath(uuid.v4());
45+
fs.outputFile(path.resolve(srcPath, `Main.${ext}`), code)
46+
.then(() => executeInDocker(imageId, ext, srcPath, compileCommand)
47+
.catch(error => {
48+
throw new CompileError(error);
49+
}))
50+
.then(() => executeInDocker(imageId, ext, srcPath, runCommand)
51+
.catch(error => {
52+
throw new RuntimeError(error);
53+
}))
4254
.then(() => res.sendFile(path.resolve(srcPath, 'traces.json')))
4355
.catch(next)
4456
.finally(() => fs.remove(srcPath));
4557
};
4658

59+
4760
router.route('/js')
4861
.get(getJsWorker);
4962

5063
router.route('/java')
5164
.post(trace({
52-
imageId: 'baekjoon/onlinejudge-java:1.8',
53-
compileCommand: 'javac -cp /usr/bin/tracers/java.jar code.java',
54-
runCommand: 'java -cp /usr/bin/tracers/java.jar:. Main',
65+
ext: 'java',
66+
imageId: 'openjdk:8',
67+
compileCommand: 'javac -cp /usr/bin/tracers/tracers.jar Main.java',
68+
runCommand: 'java -cp /usr/bin/tracers/tracers.jar:. Main',
69+
}));
70+
71+
router.route('/cpp')
72+
.post(trace({
73+
ext: 'cpp',
74+
imageId: 'gcc:8.1',
75+
compileCommand: 'g++ Main.cpp -o Main -O2 -std=c++11 -L/usr/bin/tracers/lib -l:tracers.a -I/usr/bin/tracers/include',
76+
runCommand: './Main',
5577
}));
5678

5779
export default router;

src/backend/public/algorithms

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/backend/public/codes/.gitignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/backend/public/libs/.gitignore

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)