Skip to content

Commit

Permalink
feat: generalized Node.js error handling
Browse files Browse the repository at this point in the history
gracefully handle when sketch folder has been deleted

Closes #1596

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta committed Nov 2, 2022
1 parent f6275f9 commit 12fff33
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
SHOW_PLOTTER_WINDOW,
} from '../../common/ipc-communication';
import isValidPath = require('is-valid-path');
import { ErrnoException } from '../../node/utils/errors';

app.commandLine.appendSwitch('disable-http-cache');

Expand Down Expand Up @@ -172,7 +173,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
try {
stats = await fs.stat(path);
} catch (err) {
if ('code' in err && err.code === 'ENOENT') {
if (ErrnoException.isENOENT(err)) {
return undefined;
}
throw err;
Expand Down Expand Up @@ -215,7 +216,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication {
const resolved = await fs.realpath(resolve(cwd, maybePath));
return resolved;
} catch (err) {
if ('code' in err && err.code === 'ENOENT') {
if (ErrnoException.isENOENT(err)) {
return undefined;
}
throw err;
Expand Down
3 changes: 2 additions & 1 deletion arduino-ide-extension/src/node/arduino-daemon-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BackendApplicationContribution } from '@theia/core/lib/node/backend-app
import { ArduinoDaemon, NotificationServiceServer } from '../common/protocol';
import { CLI_CONFIG } from './cli-config';
import { getExecPath, spawnCommand } from './exec-util';
import { ErrnoException } from './utils/errors';

@injectable()
export class ArduinoDaemonImpl
Expand Down Expand Up @@ -184,7 +185,7 @@ export class ArduinoDaemonImpl
}
return false;
} catch (error) {
if ('code' in error && error.code === 'ENOENT') {
if (ErrnoException.isENOENT(error)) {
return false;
}
throw error;
Expand Down
3 changes: 2 additions & 1 deletion arduino-ide-extension/src/node/config-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { DefaultCliConfig, CLI_CONFIG } from './cli-config';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { deepClone } from '@theia/core';
import { ErrnoException } from './utils/errors';

const deepmerge = require('deepmerge');

Expand Down Expand Up @@ -146,7 +147,7 @@ export class ConfigServiceImpl
const fallbackModel = await this.getFallbackCliConfig();
return deepmerge(fallbackModel, model) as DefaultCliConfig;
} catch (error) {
if ('code' in error && error.code === 'ENOENT') {
if (ErrnoException.isENOENT(error)) {
if (initializeIfAbsent) {
await this.initCliConfigTo(dirname(cliConfigPath));
return this.loadCliConfig(false);
Expand Down
9 changes: 5 additions & 4 deletions arduino-ide-extension/src/node/sketches-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
TempSketchPrefix,
} from './is-temp-sketch';
import { join } from 'path';
import { ErrnoException } from './utils/errors';

const RecentSketches = 'recent-sketches.json';
const DefaultIno = `void setup() {
Expand Down Expand Up @@ -278,7 +279,7 @@ export class SketchesServiceImpl
);
}
} catch (err) {
if ('code' in err && err.code === 'ENOENT') {
if (ErrnoException.isENOENT(err)) {
this.logger.debug(
`<<< '${RecentSketches}' does not exist yet. This is normal behavior. Falling back to empty data.`
);
Expand Down Expand Up @@ -666,7 +667,7 @@ export class SketchesServiceImpl

return this.tryParse(raw);
} catch (err) {
if ('code' in err && err.code === 'ENOENT') {
if (ErrnoException.isENOENT(err)) {
return undefined;
}
throw err;
Expand Down Expand Up @@ -695,7 +696,7 @@ export class SketchesServiceImpl
});
this.inoContent.resolve(inoContent);
} catch (err) {
if ('code' in err && err.code === 'ENOENT') {
if (ErrnoException.isENOENT(err)) {
// Ignored. The custom `.ino` blueprint file is optional.
} else {
throw err;
Expand Down Expand Up @@ -763,7 +764,7 @@ async function isInvalidSketchNameError(
.map((name) => path.join(requestSketchPath, name))[0]
);
} catch (err) {
if ('code' in err && err.code === 'ENOTDIR') {
if (ErrnoException.isENOENT(err) || ErrnoException.isENOTDIR(err)) {
return undefined;
}
throw err;
Expand Down
34 changes: 34 additions & 0 deletions arduino-ide-extension/src/node/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export type ErrnoException = Error & { code: string; errno: number };
export namespace ErrnoException {
export function is(arg: unknown): arg is ErrnoException {
if (arg instanceof Error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const error = arg as any;
return (
'code' in error &&
'errno' in error &&
typeof error['code'] === 'string' &&
typeof error['errno'] === 'number'
);
}
return false;
}

/**
* (No such file or directory): Commonly raised by `fs` operations to indicate that a component of the specified pathname does not exist — no entity (file or directory) could be found by the given path.
*/
export function isENOENT(
arg: unknown
): arg is ErrnoException & { code: 'ENOENT' } {
return is(arg) && arg.code === 'ENOENT';
}

/**
* (Not a directory): A component of the given pathname existed, but was not a directory as expected. Commonly raised by `fs.readdir`.
*/
export function isENOTDIR(
arg: unknown
): arg is ErrnoException & { code: 'ENOTDIR' } {
return is(arg) && arg.code === 'ENOTDIR';
}
}

0 comments on commit 12fff33

Please sign in to comment.