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 <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2019-10-24 15:53:55 +02:00
parent 4353bfb5b9
commit 3efb5a4e08
7 changed files with 70 additions and 68 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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<string> {
const execPath = await this.getExecPath();
return cp.execFileSync(`${execPath}`, ['version']).toString().trim();
return this.spawn(`"${execPath}"`, ['version']);
return new Promise<string>((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<Config> {
const command = await this.getExecPath();
return new Promise<Config>((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<string> {
return new Promise<string>((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;
}
});
});
}

View File

@ -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));

View File

@ -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) {

View File

@ -108,14 +108,8 @@ export class CoreClientProviderImpl implements CoreClientProvider {
const initResp = await new Promise<InitResp>(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();

View File

@ -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: