From 3efb5a4e08be8268759e771b78a576cac7afc5ba Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Thu, 24 Oct 2019 15:53:55 +0200 Subject: [PATCH] Switched from `execFile` to `spawn`. So that we can add guards against whitespaces in the path. Also cleaned up the code a bit. Signed-off-by: Akos Kitta --- .../src/browser/arduino-workspace-service.ts | 19 ---- .../arduino-frontend-application.ts | 6 +- arduino-ide-extension/src/node/arduino-cli.ts | 97 ++++++++++++------- .../src/node/arduino-daemon.ts | 2 +- .../src/node/boards-service-impl.ts | 2 - .../src/node/core-client-provider-impl.ts | 10 +- .../src/node/sketches-service-impl.ts | 2 +- 7 files changed, 70 insertions(+), 68 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-workspace-service.ts b/arduino-ide-extension/src/browser/arduino-workspace-service.ts index a56f0033..9b3a6f8d 100644 --- a/arduino-ide-extension/src/browser/arduino-workspace-service.ts +++ b/arduino-ide-extension/src/browser/arduino-workspace-service.ts @@ -1,30 +1,11 @@ import { injectable, inject } from 'inversify'; -// import { toUnix } from 'upath'; -// import URI from '@theia/core/lib/common/uri'; -// import { isWindows } from '@theia/core/lib/common/os'; import { LabelProvider } from '@theia/core/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { ConfigService } from '../common/protocol/config-service'; import { SketchesService } from '../common/protocol/sketches-service'; -// import { ArduinoAdvancedMode } from './arduino-frontend-contribution'; import { ArduinoWorkspaceRootResolver } from './arduino-workspace-resolver'; import { ArduinoAdvancedMode } from './arduino-frontend-contribution'; -/** - * This is workaround to have custom frontend binding for the default workspace, although we - * already have a custom binding for the backend. - * - * The following logic is used for determining the default workspace location: - * - #hash exists in location? - * - Yes - * - `validateHash`. Is valid sketch location? - * - Yes - * - Done. - * - No - * - `checkHistoricalWorkspaceRoots`, `try open last modified sketch`,create new sketch`. - * - No - * - `checkHistoricalWorkspaceRoots`, `try open last modified sketch`, `create new sketch`. - */ @injectable() export class ArduinoWorkspaceService extends WorkspaceService { diff --git a/arduino-ide-extension/src/browser/customization/arduino-frontend-application.ts b/arduino-ide-extension/src/browser/customization/arduino-frontend-application.ts index eb5dd642..600b4943 100644 --- a/arduino-ide-extension/src/browser/customization/arduino-frontend-application.ts +++ b/arduino-ide-extension/src/browser/customization/arduino-frontend-application.ts @@ -1,8 +1,8 @@ import { injectable, inject } from 'inversify'; -import { FileSystem } from '@theia/filesystem/lib/common'; -import { FrontendApplication } from '@theia/core/lib/browser'; +import { FileSystem } from '@theia/filesystem/lib/common/filesystem'; +import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; +import { FrontendApplication } from '@theia/core/lib/browser/frontend-application'; import { ArduinoFrontendContribution, ArduinoAdvancedMode } from '../arduino-frontend-contribution'; -import { WorkspaceService } from '@theia/workspace/lib/browser'; @injectable() export class ArduinoFrontendApplication extends FrontendApplication { diff --git a/arduino-ide-extension/src/node/arduino-cli.ts b/arduino-ide-extension/src/node/arduino-cli.ts index 8ac19866..f170be38 100644 --- a/arduino-ide-extension/src/node/arduino-cli.ts +++ b/arduino-ide-extension/src/node/arduino-cli.ts @@ -1,6 +1,6 @@ import * as os from 'os'; import * as which from 'which'; -import * as cp from 'child_process'; +import { spawn } from 'child_process'; import { join, delimiter } from 'path'; import { injectable, inject } from 'inversify'; import { ILogger } from '@theia/core'; @@ -28,43 +28,72 @@ export class ArduinoCli { async getVersion(): Promise { const execPath = await this.getExecPath(); - return cp.execFileSync(`${execPath}`, ['version']).toString().trim(); + return this.spawn(`"${execPath}"`, ['version']); + return new Promise((resolve, reject) => { + const buffers: Buffer[] = []; + const cp = spawn(`"${execPath}"`, ['version'], { windowsHide: true, shell: true }); + cp.stdout.on('data', (b: Buffer) => buffers.push(b)); + cp.on('error', error => reject(error)); + cp.on('exit', (code, signal) => { + if (code === 0) { + const result = Buffer.concat(buffers).toString('utf8').trim() + resolve(result); + return; + } + if (signal) { + reject(new Error(`Process exited with signal: ${signal}`)); + return; + } + if (code) { + reject(new Error(`Process exited with exit code: ${code}`)); + return; + } + }); + }); } async getDefaultConfig(): Promise { - const command = await this.getExecPath(); - return new Promise((resolve, reject) => { - cp.execFile( - command, - ['config', 'dump', '--format', 'json'], - { encoding: 'utf8' }, - (error, stdout, stderr) => { + const execPath = await this.getExecPath(); + const result = await this.spawn(`"${execPath}"`, ['config', 'dump', '--format', 'json']); + const { sketchbook_path, arduino_data } = JSON.parse(result); + if (!sketchbook_path) { + throw new Error(`Could not parse config. 'sketchbook_path' was missing from: ${result}`); + } + if (!arduino_data) { + throw new Error(`Could not parse config. 'arduino_data' was missing from: ${result}`); + } + return { + sketchDirUri: FileUri.create(sketchbook_path).toString(), + dataDirUri: FileUri.create(arduino_data).toString() + }; + } - if (error) { - throw error; - } - - if (stderr) { - throw new Error(stderr); - } - - const { sketchbook_path, arduino_data } = JSON.parse(stdout.trim()); - - if (!sketchbook_path) { - reject(new Error(`Could not parse config. 'sketchbook_path' was missing from: ${stdout}`)); - return; - } - - if (!arduino_data) { - reject(new Error(`Could not parse config. 'arduino_data' was missing from: ${stdout}`)); - return; - } - - resolve({ - sketchDirUri: FileUri.create(sketchbook_path).toString(), - dataDirUri: FileUri.create(arduino_data).toString() - }); - }); + private spawn(command: string, args?: string[]): Promise { + return new Promise((resolve, reject) => { + const buffers: Buffer[] = []; + const cp = spawn(command, args, { windowsHide: true, shell: true }); + cp.stdout.on('data', (b: Buffer) => buffers.push(b)); + cp.on('error', error => { + this.logger.error(`Error executing ${command} with args: ${JSON.stringify(args)}.`, error); + reject(error); + }); + cp.on('exit', (code, signal) => { + if (code === 0) { + const result = Buffer.concat(buffers).toString('utf8').trim() + resolve(result); + return; + } + if (signal) { + this.logger.error(`Unexpected signal '${signal}' when executing ${command} with args: ${JSON.stringify(args)}.`); + reject(new Error(`Process exited with signal: ${signal}`)); + return; + } + if (code) { + this.logger.error(`Unexpected exit code '${code}' when executing ${command} with args: ${JSON.stringify(args)}.`); + reject(new Error(`Process exited with exit code: ${code}`)); + return; + } + }); }); } diff --git a/arduino-ide-extension/src/node/arduino-daemon.ts b/arduino-ide-extension/src/node/arduino-daemon.ts index 1ad8ebb7..63047d16 100644 --- a/arduino-ide-extension/src/node/arduino-daemon.ts +++ b/arduino-ide-extension/src/node/arduino-daemon.ts @@ -34,7 +34,7 @@ export class ArduinoDaemon implements BackendApplicationContribution { const executable = await this.cli.getExecPath(); const version = await this.cli.getVersion(); this.logger.info(`>>> Starting ${version.toLocaleLowerCase()} daemon from ${executable}...`); - const daemon = exec(`${executable} daemon -v --log-level info --format json --log-format json`, + const daemon = exec(`"${executable}" daemon -v --log-level info --format json --log-format json`, { encoding: 'utf8', maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => { if (err || stderr) { console.log(err || new Error(stderr)); diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index 455353de..4b14a11c 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -191,8 +191,6 @@ export class BoardsServiceImpl implements BoardsService { }); } - - async search(options: { query?: string }): Promise<{ items: BoardPackage[] }> { const coreClient = await this.coreClientProvider.getClient(); if (!coreClient) { diff --git a/arduino-ide-extension/src/node/core-client-provider-impl.ts b/arduino-ide-extension/src/node/core-client-provider-impl.ts index a4dba8b9..65147596 100644 --- a/arduino-ide-extension/src/node/core-client-provider-impl.ts +++ b/arduino-ide-extension/src/node/core-client-provider-impl.ts @@ -108,14 +108,8 @@ export class CoreClientProviderImpl implements CoreClientProvider { const initResp = await new Promise(resolve => { let resp: InitResp | undefined = undefined; const stream = client.init(initReq); - stream.on('data', (data: InitResp) => { - if (!resp) { - resp = data; - } - }) - stream.on('end', () => { - resolve(resp); - }) + stream.on('data', (data: InitResp) => resp = data); + stream.on('end', () => resolve(resp)); }); const instance = initResp.getInstance(); diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index 5d5d949d..2a5ae859 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -94,7 +94,7 @@ export class SketchesServiceImpl implements SketchesService { const sketchDir = path.join(parent, sketchName) const sketchFile = path.join(sketchDir, `${sketchName}.ino`); - fs.mkdirSync(sketchDir); + fs.mkdirpSync(sketchDir); fs.writeFileSync(sketchFile, ` void setup() { // put your setup code here, to run once: