This commit is contained in:
orosmatthew 2023-05-09 19:44:26 -04:00
commit ee028c6cc8
6 changed files with 95 additions and 29 deletions

View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.4.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"tree-kill": "^1.2.2",
"vsce": "^2.15.0" "vsce": "^2.15.0"
}, },
"devDependencies": { "devDependencies": {
@ -5300,7 +5301,6 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"bin": { "bin": {
"tree-kill": "cli.js" "tree-kill": "cli.js"
} }

View File

@ -103,6 +103,7 @@
"dependencies": { "dependencies": {
"axios": "^1.4.0", "axios": "^1.4.0",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"tree-kill": "^1.2.2",
"vsce": "^2.15.0" "vsce": "^2.15.0"
} }
} }

View File

@ -17,6 +17,8 @@ export class BWPanel {
private readonly _extensionUri: vscode.Uri; private readonly _extensionUri: vscode.Uri;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
private static _context?: vscode.ExtensionContext; private static _context?: vscode.ExtensionContext;
private static _running: boolean;
private static _kill: Function | null;
public static createOrShow(context: vscode.ExtensionContext) { public static createOrShow(context: vscode.ExtensionContext) {
this._context = context; this._context = context;
@ -85,7 +87,14 @@ export class BWPanel {
this._panel.webview.html = this._getHtmlForWebview(webview); this._panel.webview.html = this._getHtmlForWebview(webview);
webview.onDidReceiveMessage(async (data) => { webview.onDidReceiveMessage(async (data) => {
switch (data.type) { switch (data.type) {
case 'onKill': {
if (!BWPanel._running || !BWPanel._kill) {
break;
}
BWPanel._kill();
}
case 'onSubmit': { case 'onSubmit': {
await vscode.workspace.saveAll();
if (!data.value) { if (!data.value) {
return; return;
} }
@ -104,20 +113,25 @@ export class BWPanel {
data.value.teamId, data.value.teamId,
data.value.problemId data.value.problemId
); );
} catch (reason) { } catch (err: any) {
vscode.window.showErrorMessage('Unable to submit'); vscode.window.showErrorMessage(err.message ?? 'Submission unsuccessful');
console.error(reason);
break; break;
} }
vscode.window.showInformationMessage('Submitted!'); vscode.window.showInformationMessage('Submitted!');
break; break;
} }
case 'onRun': { case 'onRun': {
if (BWPanel._running === true) {
vscode.window.showErrorMessage('Already running');
break;
}
await vscode.workspace.saveAll();
if (!data.value) { if (!data.value) {
return; break;
} }
const repoDir = extensionSettings().repoClonePath; const repoDir = extensionSettings().repoClonePath;
runJava( BWPanel._running = true;
const process = await runJava(
join( join(
repoDir, repoDir,
'BWContest', 'BWContest',
@ -135,11 +149,31 @@ export class BWPanel {
), ),
data.value.problemPascalName, data.value.problemPascalName,
data.value.input data.value.input
).then((output) => { );
this._panel.webview.postMessage({ type: 'onOutput', value: output }); if (!process) {
this._panel.webview.postMessage({
type: 'onOutput',
value: '[An error occurred while running]'
}); });
break; break;
} }
process.output
.then((output) => {
this._panel.webview.postMessage({ type: 'onOutput', value: output });
BWPanel._running = false;
BWPanel._kill = null;
})
.catch(() => {
this._panel.webview.postMessage({
type: 'onOutput',
value: '[An error occurred while running]'
});
BWPanel._running = false;
BWPanel._kill = null;
});
BWPanel._kill = process.kill;
break;
}
case 'onStartup': { case 'onStartup': {
const token: string | undefined = BWPanel._context?.globalState.get('token'); const token: string | undefined = BWPanel._context?.globalState.get('token');

View File

@ -5,6 +5,7 @@ import { exec, spawn } from 'child_process';
import { extensionSettings } from '../extension'; import { extensionSettings } from '../extension';
import { error } from 'console'; import { error } from 'console';
import util = require('node:util'); import util = require('node:util');
import kill = require('tree-kill');
const execPromise = util.promisify(exec); const execPromise = util.promisify(exec);
@ -13,7 +14,7 @@ export async function runJava(
mainFile: string, mainFile: string,
mainClass: string, mainClass: string,
input: string input: string
): Promise<string> { ): Promise<{ output: Promise<string>; kill: Function | null }> {
const javaPath = extensionSettings().javaPath; const javaPath = extensionSettings().javaPath;
if (javaPath == '') { if (javaPath == '') {
throw error('Java path not set'); throw error('Java path not set');
@ -30,9 +31,10 @@ export async function runJava(
const runCommand = `${join(javaPath, 'java')} -cp "${buildDir}" ${mainClass}`; const runCommand = `${join(javaPath, 'java')} -cp "${buildDir}" ${mainClass}`;
return new Promise((resolve) => {
let outputBuffer = '';
const child = spawn(runCommand, { shell: true }); const child = spawn(runCommand, { shell: true });
let outputBuffer = '';
return {
output: new Promise((resolve) => {
child.stdout.setEncoding('utf8'); child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => { child.stdout.on('data', (data) => {
outputBuffer += data.toString(); outputBuffer += data.toString();
@ -44,8 +46,29 @@ export async function runJava(
child.stdin.write(input); child.stdin.write(input);
child.stdin.end(); child.stdin.end();
let resolved = false;
child.on('close', () => { child.on('close', () => {
if (!resolved) {
resolved = true;
resolve(outputBuffer); resolve(outputBuffer);
}
}); });
});
setTimeout(() => {
if (!resolved) {
console.log('30 seconds reached, killing process');
resolved = true;
child.kill('SIGKILL');
resolve(outputBuffer + '\n[Timeout after 30 seconds]');
}
}, 30000);
}),
kill: () => {
if (child.pid) {
outputBuffer += '\n[Manually stopped]';
kill(child.pid);
}
}
};
} }

View File

@ -58,6 +58,6 @@ export async function submitProblem(
throw Error('Failed to post submission'); throw Error('Failed to post submission');
} }
if (!res.data.success) { if (!res.data.success) {
throw Error('Submission post unsuccessful'); throw Error(res.data.message ?? 'Unknown error');
} }
} }

View File

@ -61,6 +61,10 @@
} }
} }
function onKill() {
postMessage({type: 'onKill'});
}
async function fetchProblemData() { async function fetchProblemData() {
if (sessionToken) { if (sessionToken) {
const res = await fetch(`http://localhost:5173/api/contest/${sessionToken}`); const res = await fetch(`http://localhost:5173/api/contest/${sessionToken}`);
@ -125,7 +129,11 @@
{/if} {/if}
</div> </div>
<textarea bind:this={outputText} disabled /> <textarea bind:this={outputText} disabled />
{#if !running}
<button style="margin-top:5px" on:click={onRun} type="button">Run</button> <button style="margin-top:5px" on:click={onRun} type="button">Run</button>
{:else}
<button style="margin-top:5px" on:click={onKill} type="button">Stop</button>
{/if}
</div> </div>
</div> </div>
<button on:click={onSubmit} type="button">Submit</button> <button on:click={onSubmit} type="button">Submit</button>