From dfa16b714a80ee8425f918732d15b6c9073d94d6 Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Mon, 16 Oct 2023 15:32:26 -0400 Subject: [PATCH] [extension] Improve code running --- extension/bwcontest/src/SidebarProvider.ts | 5 +- extension/bwcontest/src/problemPanel.ts | 155 ++++++++++-------- extension/bwcontest/src/run/java.ts | 84 +++++----- .../webviews/components/ProblemPanel.svelte | 26 +-- 4 files changed, 154 insertions(+), 116 deletions(-) diff --git a/extension/bwcontest/src/SidebarProvider.ts b/extension/bwcontest/src/SidebarProvider.ts index 3db0ca0..d097907 100644 --- a/extension/bwcontest/src/SidebarProvider.ts +++ b/extension/bwcontest/src/SidebarProvider.ts @@ -70,8 +70,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }) }); const resData = await res.json(); - if (resData.success !== true) { - throw new Error(resData.error.message); + if (res.status !== 200 || resData.success !== true) { + vscode.window.showErrorMessage('BWContest: Invalid Login'); + return; } const sessionToken = resData.token; this.context.globalState.update('token', sessionToken); diff --git a/extension/bwcontest/src/problemPanel.ts b/extension/bwcontest/src/problemPanel.ts index 2bb4da5..65c1ed5 100644 --- a/extension/bwcontest/src/problemPanel.ts +++ b/extension/bwcontest/src/problemPanel.ts @@ -1,6 +1,10 @@ import * as vscode from 'vscode'; import { getNonce } from './getNonce'; import urlJoin from 'url-join'; +import { extensionSettings } from './extension'; +import { runJava } from './run/java'; +import { join } from 'path'; +import { TeamData } from './SidebarProvider'; export type ProblemData = { id: number; @@ -10,8 +14,21 @@ export type ProblemData = { sampleOutput: string; }[]; -export type MessageType = { msg: 'onRequestProblemData' }; -export type WebviewMessageType = { msg: 'onProblemData'; data: ProblemData }; +export type MessageType = + | { msg: 'onRequestProblemData' } + | { msg: 'onRun'; data: { problemId: number; input: string } } + | { msg: 'onKill' }; +export type WebviewMessageType = + | { msg: 'onProblemData'; data: ProblemData } + | { msg: 'onRunning' } + | { msg: 'onRunningDone' } + | { msg: 'onRunningOutput'; data: string }; + +type RunningProgram = { + problemId: number; + outputBuffer: string[]; + kill: () => void; +}; /** * Singleton class for problem panel @@ -19,8 +36,8 @@ export type WebviewMessageType = { msg: 'onProblemData'; data: ProblemData }; export class BWPanel { public static currentPanel: BWPanel | undefined; - private running: boolean = false; - // private kill: () => void | null = null; + private runningProgram: RunningProgram | undefined; + private problemData: ProblemData | undefined; private constructor( private readonly context: vscode.ExtensionContext, @@ -29,6 +46,7 @@ export class BWPanel { private readonly webUrl: string ) { this.update(); + panel.onDidDispose(() => this.dispose()); } public static show(context: vscode.ExtensionContext, webUrl: string) { @@ -37,7 +55,7 @@ export class BWPanel { : undefined; // Show panel if exists - if (BWPanel.currentPanel) { + if (BWPanel.currentPanel !== undefined) { BWPanel.currentPanel.panel.reveal(column); return; } @@ -66,7 +84,6 @@ export class BWPanel { } public dispose() { - this.panel.dispose(); BWPanel.currentPanel = undefined; } @@ -80,10 +97,13 @@ export class BWPanel { this.panel.webview.html = this._getHtmlForWebview(webview); webview.onDidReceiveMessage(async (m: MessageType) => { switch (m.msg) { - // case 'onKill': { - // if (!this.running || !this.kill) { - // break; - // } + case 'onKill': { + if (this.runningProgram !== undefined) { + this.runningProgram.kill(); + return; + } + break; + } // this.kill(); // } // case 'onSubmit': { @@ -113,66 +133,73 @@ export class BWPanel { // vscode.window.showInformationMessage('Submitted!'); // break; // } - // case 'onRun': { - // if (this.running === true) { - // vscode.window.showErrorMessage('Already running'); - // break; - // } - // await vscode.workspace.saveAll(); - // if (!data.value) { - // break; - // } - // const repoDir = extensionSettings().repoClonePath; - // this.running = true; - // const process = await runJava( - // join( - // repoDir, - // 'BWContest', - // data.value.contestId.toString(), - // data.value.teamId.toString(), - // data.value.problemPascalName.toString() - // ), - // join( - // repoDir, - // 'BWContest', - // data.value.contestId.toString(), - // data.value.teamId.toString(), - // data.value.problemPascalName.toString(), - // `${data.value.problemPascalName}.java` - // ), - // data.value.problemPascalName, - // data.value.input - // ); - // if (!process) { - // this.panel.webview.postMessage({ - // type: 'onOutput', - // value: '[An error occurred while running]' - // }); - // break; - // } - // process.output - // .then((output) => { - // this.panel.webview.postMessage({ type: 'onOutput', value: output }); - // this.running = false; - // this.kill = null; - // }) - // .catch(() => { - // this.panel.webview.postMessage({ - // type: 'onOutput', - // value: '[An error occurred while running]' - // }); - // this.running = false; - // this.kill = null; - // }); - // this.kill = process.kill; - // break; - // } + case 'onRun': { + const teamData: TeamData | undefined = this.context.globalState.get('teamData'); + if (teamData === undefined) { + return; + } + if (this.problemData === undefined) { + return; + } + if (this.runningProgram !== undefined) { + vscode.window.showErrorMessage('Already Running'); + return; + } + const problem = this.problemData.find((p) => (p.id = m.data.problemId)); + if (problem === undefined) { + return; + } + await vscode.workspace.saveAll(); + const repoDir = extensionSettings().repoClonePath; + const outputBuffer: string[] = []; + this.webviewPostMessage({ msg: 'onRunning' }); + this.webviewPostMessage({ msg: 'onRunningOutput', data: '[Compiling...]' }); + + const killFunc = await runJava( + join( + repoDir, + 'BWContest', + teamData.contestId.toString(), + teamData.teamId.toString(), + problem.pascalName + ), + join( + repoDir, + 'BWContest', + teamData.contestId.toString(), + teamData.teamId.toString(), + problem.pascalName, + `${problem.pascalName}.java` + ), + problem.pascalName, + m.data.input, + (data: string) => { + outputBuffer.push(data); + this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') }); + }, + () => { + this.runningProgram = undefined; + this.webviewPostMessage({ msg: 'onRunningDone' }); + } + ); + if (killFunc !== undefined) { + this.runningProgram = { + problemId: m.data.problemId, + outputBuffer: outputBuffer, + kill: killFunc + }; + } else { + this.webviewPostMessage({ msg: 'onRunningDone' }); + } + break; + } case 'onRequestProblemData': { const token: string | undefined = this.context.globalState.get('token'); if (token !== undefined) { const res = await fetch(urlJoin(this.webUrl, `/api/contest/${token}`)); const data = await res.json(); if (data.success === true) { + this.problemData = data.problems; this.webviewPostMessage({ msg: 'onProblemData', data: data.problems diff --git a/extension/bwcontest/src/run/java.ts b/extension/bwcontest/src/run/java.ts index 9a5c3d3..181618b 100644 --- a/extension/bwcontest/src/run/java.ts +++ b/extension/bwcontest/src/run/java.ts @@ -13,62 +13,72 @@ export async function runJava( srcDir: string, mainFile: string, mainClass: string, - input: string -): Promise<{ output: Promise; kill: () => void | null }> { + input: string, + outputCallback: (data: string) => void, + doneCallback: () => void +): Promise<(() => void) | undefined> { const javaPath = extensionSettings().javaPath; if (javaPath == '') { throw error('Java path not set'); } const tempDir = os.tmpdir(); const buildDir = join(tempDir, 'bwcontest_java'); - if (fs.existsSync(buildDir)) { - fs.removeSync(buildDir); + if (await fs.exists(buildDir)) { + await fs.remove(buildDir); } - fs.mkdirSync(buildDir); + await fs.mkdir(buildDir); const compileCommand = `${join(javaPath, 'javac')} -cp ${srcDir} ${mainFile} -d ${buildDir}`; - await execPromise(compileCommand); + try { + await execPromise(compileCommand); + } catch (error) { + outputCallback('[Compile Error]\n\n'); + outputCallback(String(error)); + return; + } const runCommand = `${join(javaPath, 'java')} -cp "${buildDir}" ${mainClass}`; const child = spawn(runCommand, { shell: true }); - let outputBuffer = ''; - return { - output: new Promise((resolve) => { - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data) => { - outputBuffer += data.toString(); - }); - child.stderr.setEncoding('utf8'); - child.stderr.on('data', (data) => { - outputBuffer += data.toString(); - }); - child.stdin.write(input); - child.stdin.end(); - let resolved = false; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputCallback(data.toString()); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputCallback(data.toString()); + }); + child.stdin.write(input); + child.stdin.end(); - child.on('close', () => { - if (!resolved) { - resolved = true; - resolve(outputBuffer); - } - }); + let done = false; - setTimeout(() => { - if (!resolved) { - console.log('30 seconds reached, killing process'); - resolved = true; - child.kill('SIGKILL'); - resolve(outputBuffer + '\n[Timeout after 30 seconds]'); - } - }, 30000); - }), - kill: () => { + child.on('close', () => { + if (done === false) { + done = true; + doneCallback(); + } + }); + + setTimeout(() => { + if (done === false) { + console.log('\n[30 seconds reached, killing process...]'); + done = true; if (child.pid) { - outputBuffer += '\n[Manually stopped]'; kill(child.pid); } + outputCallback('\n[Timeout after 30 seconds]'); + doneCallback(); + } + }, 30000); + + return () => { + if (child.pid) { + done = true; + kill(child.pid); + outputCallback('\n[Manually stopped]'); + doneCallback(); } }; } diff --git a/extension/bwcontest/webviews/components/ProblemPanel.svelte b/extension/bwcontest/webviews/components/ProblemPanel.svelte index c8b1dd1..3406476 100644 --- a/extension/bwcontest/webviews/components/ProblemPanel.svelte +++ b/extension/bwcontest/webviews/components/ProblemPanel.svelte @@ -29,15 +29,12 @@ } function onRun() { - // if (problemData !== undefined && running === false) { - // postMessage({ - // type: 'requestRun', - // value: { - // problemId: problemData[activeProblemIndex].id, - // input: sampleInputValue - // } - // }); - // } + if (problemData !== undefined) { + postMessage({ + msg: 'onRun', + data: { input: sampleInputValue, problemId: problemData[activeProblemIndex].id } + }); + } } function updateTextBoxes() { @@ -68,7 +65,7 @@ } function onKill() { - // postMessage({ type: 'onKill' }); + postMessage({ msg: 'onKill' }); } onMount(() => { @@ -77,12 +74,15 @@ window.addEventListener('message', async (event) => { const m = (event as MessageEvent).data as WebviewMessageType; - // if (message.msg === 'onOutput') { - // outputValue = message.value; - // running = false; if (m.msg === 'onProblemData') { problemData = m.data; updateTextBoxes(); + } else if (m.msg === 'onRunning') { + running = true; + } else if (m.msg === 'onRunningDone') { + running = false; + } else if (m.msg === 'onRunningOutput') { + outputValue = m.data; } });