Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cef1ccb

Browse files
author
Akos Kitta
committedFeb 20, 2024
feat: cancelable verify+upload
Closes #1199 Signed-off-by: Akos Kitta <[email protected]>
1 parent c18f23e commit cef1ccb

File tree

13 files changed

+307
-180
lines changed

13 files changed

+307
-180
lines changed
 

‎arduino-ide-extension/src/browser/contributions/burn-bootloader.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@ export class BurnBootloader extends CoreServiceContribution {
3737
'arduino/bootloader/burningBootloader',
3838
'Burning bootloader...'
3939
),
40-
task: (progressId, coreService) =>
41-
coreService.burnBootloader({
42-
...options,
43-
progressId,
44-
}),
40+
task: (progressId, coreService, token) =>
41+
coreService.burnBootloader(
42+
{
43+
...options,
44+
progressId,
45+
},
46+
token
47+
),
48+
cancelable: true,
4549
});
4650
this.messageService.info(
4751
nls.localize(

‎arduino-ide-extension/src/browser/contributions/contribution.ts

Lines changed: 72 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,89 @@
1+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
12
import {
2-
inject,
3-
injectable,
4-
interfaces,
5-
postConstruct,
6-
} from '@theia/core/shared/inversify';
7-
import URI from '@theia/core/lib/common/uri';
8-
import { ILogger } from '@theia/core/lib/common/logger';
9-
import {
10-
Disposable,
11-
DisposableCollection,
12-
} from '@theia/core/lib/common/disposable';
13-
import { Saveable } from '@theia/core/lib/browser/saveable';
14-
import { FileService } from '@theia/filesystem/lib/browser/file-service';
15-
import { MaybePromise } from '@theia/core/lib/common/types';
16-
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
17-
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
18-
import { MessageService } from '@theia/core/lib/common/message-service';
19-
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
20-
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';
21-
import {
22-
MenuModelRegistry,
23-
MenuContribution,
24-
} from '@theia/core/lib/common/menu';
3+
FrontendApplication,
4+
FrontendApplicationContribution,
5+
} from '@theia/core/lib/browser/frontend-application';
6+
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
257
import {
26-
KeybindingRegistry,
278
KeybindingContribution,
9+
KeybindingRegistry,
2810
} from '@theia/core/lib/browser/keybinding';
11+
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
12+
import { OpenerService, open } from '@theia/core/lib/browser/opener-service';
13+
import { Saveable } from '@theia/core/lib/browser/saveable';
14+
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
2915
import {
3016
TabBarToolbarContribution,
3117
TabBarToolbarRegistry,
3218
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
33-
import {
34-
FrontendApplicationContribution,
35-
FrontendApplication,
36-
} from '@theia/core/lib/browser/frontend-application';
19+
import { CancellationToken } from '@theia/core/lib/common/cancellation';
3720
import {
3821
Command,
39-
CommandRegistry,
4022
CommandContribution,
23+
CommandRegistry,
4124
CommandService,
4225
} from '@theia/core/lib/common/command';
43-
import { SettingsService } from '../dialogs/settings/settings';
4426
import {
45-
CurrentSketch,
46-
SketchesServiceClientImpl,
47-
} from '../sketches-service-client-impl';
27+
Disposable,
28+
DisposableCollection,
29+
} from '@theia/core/lib/common/disposable';
30+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
31+
import { ILogger } from '@theia/core/lib/common/logger';
32+
import {
33+
MenuContribution,
34+
MenuModelRegistry,
35+
} from '@theia/core/lib/common/menu';
36+
import { MessageService } from '@theia/core/lib/common/message-service';
37+
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
38+
import { nls } from '@theia/core/lib/common/nls';
39+
import { MaybePromise, isObject } from '@theia/core/lib/common/types';
40+
import URI from '@theia/core/lib/common/uri';
41+
import {
42+
inject,
43+
injectable,
44+
interfaces,
45+
postConstruct,
46+
} from '@theia/core/shared/inversify';
47+
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
48+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
49+
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
50+
import { OutputChannelSeverity } from '@theia/output/lib/browser/output-channel';
51+
import { MainMenuManager } from '../../common/main-menu-manager';
52+
import { userAbort } from '../../common/nls';
4853
import {
49-
SketchesService,
50-
FileSystemExt,
51-
Sketch,
52-
CoreService,
5354
CoreError,
55+
CoreService,
56+
FileSystemExt,
5457
ResponseServiceClient,
58+
Sketch,
59+
SketchesService,
5560
} from '../../common/protocol';
61+
import {
62+
ExecuteWithProgress,
63+
UserAbortApplicationError,
64+
} from '../../common/protocol/progressible';
5665
import { ArduinoPreferences } from '../arduino-preferences';
57-
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
58-
import { nls } from '@theia/core';
59-
import { OutputChannelManager } from '../theia/output/output-channel';
60-
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
61-
import { ExecuteWithProgress } from '../../common/protocol/progressible';
62-
import { BoardsServiceProvider } from '../boards/boards-service-provider';
6366
import { BoardsDataStore } from '../boards/boards-data-store';
64-
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
65-
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
66-
import { WorkspaceService } from '../theia/workspace/workspace-service';
67-
import { MainMenuManager } from '../../common/main-menu-manager';
67+
import { BoardsServiceProvider } from '../boards/boards-service-provider';
6868
import { ConfigServiceClient } from '../config/config-service-client';
69-
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
7069
import { DialogService } from '../dialog-service';
70+
import { SettingsService } from '../dialogs/settings/settings';
71+
import {
72+
CurrentSketch,
73+
SketchesServiceClientImpl,
74+
} from '../sketches-service-client-impl';
7175
import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service';
76+
import { OutputChannelManager } from '../theia/output/output-channel';
77+
import { WorkspaceService } from '../theia/workspace/workspace-service';
7278

7379
export {
7480
Command,
7581
CommandRegistry,
76-
MenuModelRegistry,
7782
KeybindingRegistry,
83+
MenuModelRegistry,
84+
Sketch,
7885
TabBarToolbarRegistry,
7986
URI,
80-
Sketch,
8187
open,
8288
};
8389

@@ -247,6 +253,12 @@ export abstract class CoreServiceContribution extends SketchContribution {
247253
}
248254

249255
protected handleError(error: unknown): void {
256+
if (isObject(error) && UserAbortApplicationError.is(error)) {
257+
this.outputChannelManager
258+
.getChannel('Arduino')
259+
.appendLine(userAbort, OutputChannelSeverity.Warning);
260+
return;
261+
}
250262
this.tryToastErrorMessage(error);
251263
}
252264

@@ -293,7 +305,13 @@ export abstract class CoreServiceContribution extends SketchContribution {
293305
protected async doWithProgress<T>(options: {
294306
progressText: string;
295307
keepOutput?: boolean;
296-
task: (progressId: string, coreService: CoreService) => Promise<T>;
308+
task: (
309+
progressId: string,
310+
coreService: CoreService,
311+
cancellationToken?: CancellationToken
312+
) => Promise<T>;
313+
// false by default
314+
cancelable?: boolean;
297315
}): Promise<T> {
298316
const toDisposeOnComplete = new DisposableCollection(
299317
this.maybeActivateMonitorWidget()
@@ -306,8 +324,10 @@ export abstract class CoreServiceContribution extends SketchContribution {
306324
messageService: this.messageService,
307325
responseService: this.responseService,
308326
progressText,
309-
run: ({ progressId }) => task(progressId, this.coreService),
327+
run: ({ progressId, cancellationToken }) =>
328+
task(progressId, this.coreService, cancellationToken),
310329
keepOutput,
330+
cancelable: options.cancelable,
311331
});
312332
toDisposeOnComplete.dispose();
313333
return result;

‎arduino-ide-extension/src/browser/contributions/upload-sketch.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ export class UploadSketch extends CoreServiceContribution {
136136

137137
const uploadResponse = await this.doWithProgress({
138138
progressText: nls.localize('arduino/sketch/uploading', 'Uploading...'),
139-
task: (progressId, coreService) =>
140-
coreService.upload({ ...uploadOptions, progressId }),
139+
task: (progressId, coreService, token) =>
140+
coreService.upload({ ...uploadOptions, progressId }, token),
141141
keepOutput: true,
142+
cancelable: true,
142143
});
143144
// the port update is NOOP if nothing has changed
144145
this.boardsServiceProvider.updateConfig(uploadResponse.portAfterUpload);

‎arduino-ide-extension/src/browser/contributions/verify-sketch.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { inject, injectable } from '@theia/core/shared/inversify';
21
import { Emitter } from '@theia/core/lib/common/event';
2+
import { nls } from '@theia/core/lib/common/nls';
3+
import { inject, injectable } from '@theia/core/shared/inversify';
4+
import type { CoreService } from '../../common/protocol';
35
import { ArduinoMenus } from '../menu/arduino-menus';
6+
import { CurrentSketch } from '../sketches-service-client-impl';
47
import { ArduinoToolbar } from '../toolbar/arduino-toolbar';
58
import {
6-
CoreServiceContribution,
79
Command,
810
CommandRegistry,
9-
MenuModelRegistry,
11+
CoreServiceContribution,
1012
KeybindingRegistry,
13+
MenuModelRegistry,
1114
TabBarToolbarRegistry,
1215
} from './contribution';
13-
import { nls } from '@theia/core/lib/common';
14-
import { CurrentSketch } from '../sketches-service-client-impl';
15-
import { CoreService } from '../../common/protocol';
1616
import { CoreErrorHandler } from './core-error-handler';
1717

1818
export interface VerifySketchParams {
@@ -131,11 +131,15 @@ export class VerifySketch extends CoreServiceContribution {
131131
'arduino/sketch/compile',
132132
'Compiling sketch...'
133133
),
134-
task: (progressId, coreService) =>
135-
coreService.compile({
136-
...options,
137-
progressId,
138-
}),
134+
task: (progressId, coreService, token) =>
135+
coreService.compile(
136+
{
137+
...options,
138+
progressId,
139+
},
140+
token
141+
),
142+
cancelable: true,
139143
});
140144
this.messageService.info(
141145
nls.localize('arduino/sketch/doneCompiling', 'Done compiling.'),

‎arduino-ide-extension/src/browser/library/library-list-widget.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ import {
1212
LibrarySearch,
1313
LibraryService,
1414
} from '../../common/protocol/library-service';
15-
import {
16-
ListWidget,
17-
UserAbortError,
18-
} from '../widgets/component-list/list-widget';
15+
import { ListWidget } from '../widgets/component-list/list-widget';
1916
import { Installable } from '../../common/protocol';
2017
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';
2118
import { nls } from '@theia/core/lib/common';
2219
import { LibraryFilterRenderer } from '../widgets/component-list/filter-renderer';
2320
import { findChildTheiaButton, splitByBoldTag } from '../utils/dom';
21+
import { UserAbortError } from '../../common/protocol/progressible';
2422

2523
@injectable()
2624
export class LibraryListWidget extends ListWidget<

‎arduino-ide-extension/src/browser/widgets/component-list/component-list-item.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from '@theia/core/shared/react';
22
import type { ArduinoComponent } from '../../../common/protocol/arduino-component';
33
import { Installable } from '../../../common/protocol/installable';
44
import type { ListItemRenderer } from './list-item-renderer';
5-
import { UserAbortError } from './list-widget';
5+
import { UserAbortError } from '../../../common/protocol/progressible';
66

77
export class ComponentListItem<
88
T extends ArduinoComponent

‎arduino-ide-extension/src/browser/widgets/component-list/filterable-list-container.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import { CommandService } from '@theia/core/lib/common/command';
55
import { MessageService } from '@theia/core/lib/common/message-service';
66
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
77
import { Searchable } from '../../../common/protocol/searchable';
8-
import { ExecuteWithProgress } from '../../../common/protocol/progressible';
8+
import {
9+
ExecuteWithProgress,
10+
UserAbortError,
11+
} from '../../../common/protocol/progressible';
912
import {
1013
Installable,
1114
libraryInstallFailed,
1215
platformInstallFailed,
1316
} from '../../../common/protocol/installable';
1417
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
1518
import { SearchBar } from './search-bar';
16-
import { ListWidget, UserAbortError } from './list-widget';
19+
import { ListWidget } from './list-widget';
1720
import { ComponentList } from './component-list';
1821
import { ListItemRenderer } from './list-item-renderer';
1922
import {

‎arduino-ide-extension/src/browser/widgets/component-list/list-widget.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,3 @@ export namespace ListWidget {
192192
readonly defaultSearchOptions: S;
193193
}
194194
}
195-
196-
export class UserAbortError extends Error {
197-
constructor(message = 'User abort') {
198-
super(message);
199-
Object.setPrototypeOf(this, UserAbortError.prototype);
200-
}
201-
}

‎arduino-ide-extension/src/common/nls.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ export const noSketchOpened = nls.localize(
3939
'arduino/common/noSketchOpened',
4040
'No sketch opened'
4141
);
42+
43+
export const userAbort = nls.localize('arduino/common/userAbort', 'User abort');

‎arduino-ide-extension/src/common/protocol/core-service.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ApplicationError } from '@theia/core/lib/common/application-error';
2+
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
23
import { nls } from '@theia/core/lib/common/nls';
34
import type {
45
Location,
@@ -7,7 +8,7 @@ import type {
78
} from '@theia/core/shared/vscode-languageserver-protocol';
89
import type { CompileSummary as ApiCompileSummary } from 'vscode-arduino-api';
910
import type { BoardUserField, Installable } from '../../common/protocol/';
10-
import { isPortIdentifier, PortIdentifier, Programmer } from './boards-service';
11+
import { PortIdentifier, Programmer, isPortIdentifier } from './boards-service';
1112
import type { IndexUpdateSummary } from './notification-service';
1213
import type { Sketch } from './sketches-service';
1314

@@ -162,9 +163,18 @@ export function isUploadResponse(arg: unknown): arg is UploadResponse {
162163
export const CoreServicePath = '/services/core-service';
163164
export const CoreService = Symbol('CoreService');
164165
export interface CoreService {
165-
compile(options: CoreService.Options.Compile): Promise<void>;
166-
upload(options: CoreService.Options.Upload): Promise<UploadResponse>;
167-
burnBootloader(options: CoreService.Options.Bootloader): Promise<void>;
166+
compile(
167+
options: CoreService.Options.Compile,
168+
cancellationToken?: CancellationToken
169+
): Promise<void>;
170+
upload(
171+
options: CoreService.Options.Upload,
172+
cancellationToken?: CancellationToken
173+
): Promise<UploadResponse>;
174+
burnBootloader(
175+
options: CoreService.Options.Bootloader,
176+
cancellationToken?: CancellationToken
177+
): Promise<void>;
168178
/**
169179
* Refreshes the underling core gRPC client for the Arduino CLI.
170180
*/

‎arduino-ide-extension/src/common/protocol/progressible.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,48 @@
1+
import { ApplicationError } from '@theia/core/lib/common/application-error';
12
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
23
import { CancellationTokenSource } from '@theia/core/lib/common/cancellation';
34
import type { MessageService } from '@theia/core/lib/common/message-service';
45
import type { Progress } from '@theia/core/lib/common/message-service-protocol';
6+
import { userAbort } from '../nls';
57
import type { ResponseServiceClient } from './response-service';
68

9+
export const UserAbortApplicationError = ApplicationError.declare(
10+
9999,
11+
(message: string, uri: string) => {
12+
return {
13+
message,
14+
data: { uri },
15+
};
16+
}
17+
);
18+
19+
export class UserAbortError extends Error {
20+
constructor() {
21+
super(userAbort);
22+
Object.setPrototypeOf(this, UserAbortError.prototype);
23+
}
24+
}
25+
726
export namespace ExecuteWithProgress {
827
export async function doWithProgress<T>(options: {
9-
run: ({ progressId }: { progressId: string }) => Promise<T>;
28+
run: ({
29+
progressId,
30+
cancellationToken,
31+
}: {
32+
progressId: string;
33+
cancellationToken?: CancellationToken;
34+
}) => Promise<T>;
1035
messageService: MessageService;
1136
responseService: ResponseServiceClient;
1237
progressText: string;
1338
keepOutput?: boolean;
39+
cancelable?: boolean;
1440
}): Promise<T> {
1541
return withProgress(
1642
options.progressText,
1743
options.messageService,
1844
// eslint-disable-next-line @typescript-eslint/no-unused-vars
19-
async (progress, _token) => {
45+
async (progress, token) => {
2046
const progressId = progress.id;
2147
const toDispose = options.responseService.onProgressDidChange(
2248
(progressMessage) => {
@@ -30,24 +56,29 @@ export namespace ExecuteWithProgress {
3056
if (!options.keepOutput) {
3157
options.responseService.clearOutput();
3258
}
33-
const result = await options.run({ progressId });
59+
const result = await options.run({
60+
progressId,
61+
cancellationToken: token,
62+
});
3463
return result;
3564
} finally {
3665
toDispose.dispose();
3766
}
38-
}
67+
},
68+
options.cancelable
3969
);
4070
}
4171

4272
export async function withProgress<T>(
4373
text: string,
4474
messageService: MessageService,
45-
cb: (progress: Progress, token: CancellationToken) => Promise<T>
75+
cb: (progress: Progress, token: CancellationToken) => Promise<T>,
76+
cancelable = false
4677
): Promise<T> {
4778
const cancellationSource = new CancellationTokenSource();
4879
const { token } = cancellationSource;
4980
const progress = await messageService.showProgress(
50-
{ text, options: { cancelable: false } },
81+
{ text, options: { cancelable } },
5182
() => cancellationSource.cancel()
5283
);
5384
try {

‎arduino-ide-extension/src/node/core-service-impl.ts

Lines changed: 145 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,44 @@
1+
import type { ClientReadableStream } from '@grpc/grpc-js';
2+
import { ApplicationError } from '@theia/core/lib/common/application-error';
3+
import type { CancellationToken } from '@theia/core/lib/common/cancellation';
4+
import { CommandService } from '@theia/core/lib/common/command';
5+
import {
6+
Disposable,
7+
DisposableCollection,
8+
} from '@theia/core/lib/common/disposable';
9+
import { nls } from '@theia/core/lib/common/nls';
10+
import type { Mutable } from '@theia/core/lib/common/types';
111
import { FileUri } from '@theia/core/lib/node/file-uri';
212
import { inject, injectable } from '@theia/core/shared/inversify';
3-
import { relative } from 'node:path';
413
import * as jspb from 'google-protobuf';
514
import { BoolValue } from 'google-protobuf/google/protobuf/wrappers_pb';
6-
import type { ClientReadableStream } from '@grpc/grpc-js';
15+
import path from 'node:path';
716
import {
17+
UploadResponse as ApiUploadResponse,
18+
OutputMessage,
19+
Port,
20+
PortIdentifier,
21+
resolveDetectedPort,
22+
} from '../common/protocol';
23+
import {
24+
CompileSummary,
825
CompilerWarnings,
9-
CoreService,
1026
CoreError,
11-
CompileSummary,
27+
CoreService,
1228
isCompileSummary,
1329
isUploadResponse,
1430
} from '../common/protocol/core-service';
31+
import { ResponseService } from '../common/protocol/response-service';
32+
import { firstToUpperCase, notEmpty } from '../common/utils';
33+
import { BoardDiscovery, createApiPort } from './board-discovery';
34+
import { tryParseError } from './cli-error-parser';
35+
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
36+
import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
1537
import {
1638
CompileRequest,
1739
CompileResponse,
1840
} from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb';
19-
import { CoreClientAware } from './core-client-provider';
41+
import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
2042
import {
2143
BurnBootloaderRequest,
2244
BurnBootloaderResponse,
@@ -25,26 +47,13 @@ import {
2547
UploadUsingProgrammerRequest,
2648
UploadUsingProgrammerResponse,
2749
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
28-
import { ResponseService } from '../common/protocol/response-service';
29-
import {
30-
resolveDetectedPort,
31-
OutputMessage,
32-
PortIdentifier,
33-
Port,
34-
UploadResponse as ApiUploadResponse,
35-
} from '../common/protocol';
36-
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
37-
import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
38-
import { ApplicationError, CommandService, Disposable, nls } from '@theia/core';
50+
import { CoreClientAware } from './core-client-provider';
51+
import { ExecuteWithProgress, ProgressResponse } from './grpc-progressible';
3952
import { MonitorManager } from './monitor-manager';
40-
import { AutoFlushingBuffer } from './utils/buffers';
41-
import { tryParseError } from './cli-error-parser';
42-
import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
43-
import { firstToUpperCase, notEmpty } from '../common/utils';
4453
import { ServiceError } from './service-error';
45-
import { ExecuteWithProgress, ProgressResponse } from './grpc-progressible';
46-
import type { Mutable } from '@theia/core/lib/common/types';
47-
import { BoardDiscovery, createApiPort } from './board-discovery';
54+
import { AutoFlushingBuffer } from './utils/buffers';
55+
import { userAbort } from '../common/nls';
56+
import { UserAbortApplicationError } from '../common/protocol/progressible';
4857

4958
namespace Uploadable {
5059
export type Request = UploadRequest | UploadUsingProgrammerRequest;
@@ -64,9 +73,13 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
6473
@inject(BoardDiscovery)
6574
private readonly boardDiscovery: BoardDiscovery;
6675

67-
async compile(options: CoreService.Options.Compile): Promise<void> {
76+
async compile(
77+
options: CoreService.Options.Compile,
78+
cancellationToken?: CancellationToken
79+
): Promise<void> {
6880
const coreClient = await this.coreClient;
6981
const { client, instance } = coreClient;
82+
const request = this.compileRequest(options, instance);
7083
const compileSummary = <CompileSummaryFragment>{};
7184
const progressHandler = this.createProgressHandler(options);
7285
const compileSummaryHandler = (response: CompileResponse) =>
@@ -75,10 +88,15 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
7588
progressHandler,
7689
compileSummaryHandler
7790
);
78-
const request = this.compileRequest(options, instance);
91+
const toDisposeOnFinally = new DisposableCollection(handler);
7992
return new Promise<void>((resolve, reject) => {
80-
client
81-
.compile(request)
93+
const call = client.compile(request);
94+
if (cancellationToken) {
95+
toDisposeOnFinally.push(
96+
cancellationToken.onCancellationRequested(() => call.cancel())
97+
);
98+
}
99+
call
82100
.on('data', handler.onData)
83101
.on('error', (error) => {
84102
if (!ServiceError.is(error)) {
@@ -87,30 +105,39 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
87105
error
88106
);
89107
reject(error);
90-
} else {
91-
const compilerErrors = tryParseError({
92-
content: handler.content,
93-
sketch: options.sketch,
94-
});
95-
const message = nls.localize(
96-
'arduino/compile/error',
97-
'Compilation error: {0}',
98-
compilerErrors
99-
.map(({ message }) => message)
100-
.filter(notEmpty)
101-
.shift() ?? error.details
102-
);
103-
this.sendResponse(
104-
error.details + '\n\n' + message,
105-
OutputMessage.Severity.Error
106-
);
107-
reject(CoreError.VerifyFailed(message, compilerErrors));
108+
return;
108109
}
110+
if (ServiceError.isCancel(error)) {
111+
console.log(userAbort);
112+
reject(UserAbortApplicationError());
113+
return;
114+
}
115+
const compilerErrors = tryParseError({
116+
content: handler.content,
117+
sketch: options.sketch,
118+
});
119+
const message = nls.localize(
120+
'arduino/compile/error',
121+
'Compilation error: {0}',
122+
compilerErrors
123+
.map(({ message }) => message)
124+
.filter(notEmpty)
125+
.shift() ?? error.details
126+
);
127+
this.sendResponse(
128+
error.details + '\n\n' + message,
129+
OutputMessage.Severity.Error
130+
);
131+
reject(CoreError.VerifyFailed(message, compilerErrors));
109132
})
110133
.on('end', resolve);
111134
}).finally(() => {
112-
handler.dispose();
135+
toDisposeOnFinally.dispose();
113136
if (!isCompileSummary(compileSummary)) {
137+
if (cancellationToken && cancellationToken.isCancellationRequested) {
138+
// NOOP
139+
return;
140+
}
114141
console.error(
115142
`Have not received the full compile summary from the CLI while running the compilation. ${JSON.stringify(
116143
compileSummary
@@ -176,7 +203,10 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
176203
return request;
177204
}
178205

179-
upload(options: CoreService.Options.Upload): Promise<ApiUploadResponse> {
206+
upload(
207+
options: CoreService.Options.Upload,
208+
cancellationToken?: CancellationToken
209+
): Promise<ApiUploadResponse> {
180210
const { usingProgrammer } = options;
181211
return this.doUpload(
182212
options,
@@ -190,7 +220,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
190220
usingProgrammer
191221
? CoreError.UploadUsingProgrammerFailed
192222
: CoreError.UploadFailed,
193-
`upload${usingProgrammer ? ' using programmer' : ''}`
223+
`upload${usingProgrammer ? ' using programmer' : ''}`,
224+
cancellationToken
194225
);
195226
}
196227

@@ -204,7 +235,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
204235
client: ArduinoCoreServiceClient
205236
) => (request: REQ) => ClientReadableStream<RESP>,
206237
errorCtor: ApplicationError.Constructor<number, CoreError.ErrorLocation[]>,
207-
task: string
238+
task: string,
239+
cancellationToken?: CancellationToken
208240
): Promise<ApiUploadResponse> {
209241
const portBeforeUpload = options.port;
210242
const uploadResponseFragment: Mutable<Partial<ApiUploadResponse>> = {
@@ -241,33 +273,47 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
241273
progressHandler,
242274
updateUploadResponseFragmentHandler
243275
);
276+
const toDisposeOnFinally = new DisposableCollection(handler);
244277
const grpcCall = responseFactory(client);
245278
return this.notifyUploadWillStart(options).then(() =>
246279
new Promise<ApiUploadResponse>((resolve, reject) => {
247-
grpcCall(this.initUploadRequest(request, options, instance))
280+
const call = grpcCall(
281+
this.initUploadRequest(request, options, instance)
282+
);
283+
if (cancellationToken) {
284+
toDisposeOnFinally.push(
285+
cancellationToken.onCancellationRequested(() => call.cancel())
286+
);
287+
}
288+
call
248289
.on('data', handler.onData)
249290
.on('error', (error) => {
250291
if (!ServiceError.is(error)) {
251292
console.error(`Unexpected error occurred while ${task}.`, error);
252293
reject(error);
253-
} else {
254-
const message = nls.localize(
255-
'arduino/upload/error',
256-
'{0} error: {1}',
257-
firstToUpperCase(task),
258-
error.details
259-
);
260-
this.sendResponse(error.details, OutputMessage.Severity.Error);
261-
reject(
262-
errorCtor(
263-
message,
264-
tryParseError({
265-
content: handler.content,
266-
sketch: options.sketch,
267-
})
268-
)
269-
);
294+
return;
295+
}
296+
if (ServiceError.isCancel(error)) {
297+
console.log(userAbort);
298+
reject(UserAbortApplicationError());
299+
return;
270300
}
301+
const message = nls.localize(
302+
'arduino/upload/error',
303+
'{0} error: {1}',
304+
firstToUpperCase(task),
305+
error.details
306+
);
307+
this.sendResponse(error.details, OutputMessage.Severity.Error);
308+
reject(
309+
errorCtor(
310+
message,
311+
tryParseError({
312+
content: handler.content,
313+
sketch: options.sketch,
314+
})
315+
)
316+
);
271317
})
272318
.on('end', () => {
273319
if (isUploadResponse(uploadResponseFragment)) {
@@ -285,7 +331,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
285331
}
286332
});
287333
}).finally(async () => {
288-
handler.dispose();
334+
toDisposeOnFinally.dispose();
289335
await this.notifyUploadDidFinish(
290336
Object.assign(options, {
291337
afterPort: uploadResponseFragment.portAfterUpload,
@@ -320,16 +366,25 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
320366
return request;
321367
}
322368

323-
async burnBootloader(options: CoreService.Options.Bootloader): Promise<void> {
369+
async burnBootloader(
370+
options: CoreService.Options.Bootloader,
371+
cancellationToken?: CancellationToken
372+
): Promise<void> {
324373
const coreClient = await this.coreClient;
325374
const { client, instance } = coreClient;
326375
const progressHandler = this.createProgressHandler(options);
327376
const handler = this.createOnDataHandler(progressHandler);
328377
const request = this.burnBootloaderRequest(options, instance);
378+
const toDisposeOnFinally = new DisposableCollection(handler);
329379
return this.notifyUploadWillStart(options).then(() =>
330380
new Promise<void>((resolve, reject) => {
331-
client
332-
.burnBootloader(request)
381+
const call = client.burnBootloader(request);
382+
if (cancellationToken) {
383+
toDisposeOnFinally.push(
384+
cancellationToken.onCancellationRequested(() => call.cancel())
385+
);
386+
}
387+
call
333388
.on('data', handler.onData)
334389
.on('error', (error) => {
335390
if (!ServiceError.is(error)) {
@@ -338,23 +393,28 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
338393
error
339394
);
340395
reject(error);
341-
} else {
342-
this.sendResponse(error.details, OutputMessage.Severity.Error);
343-
reject(
344-
CoreError.BurnBootloaderFailed(
345-
nls.localize(
346-
'arduino/burnBootloader/error',
347-
'Error while burning the bootloader: {0}',
348-
error.details
349-
),
350-
tryParseError({ content: handler.content })
351-
)
352-
);
396+
return;
353397
}
398+
if (ServiceError.isCancel(error)) {
399+
console.log(userAbort);
400+
reject(UserAbortApplicationError());
401+
return;
402+
}
403+
this.sendResponse(error.details, OutputMessage.Severity.Error);
404+
reject(
405+
CoreError.BurnBootloaderFailed(
406+
nls.localize(
407+
'arduino/burnBootloader/error',
408+
'Error while burning the bootloader: {0}',
409+
error.details
410+
),
411+
tryParseError({ content: handler.content })
412+
)
413+
);
354414
})
355415
.on('end', resolve);
356416
}).finally(async () => {
357-
handler.dispose();
417+
toDisposeOnFinally.dispose();
358418
await this.notifyUploadDidFinish(
359419
Object.assign(options, { afterPort: options.port })
360420
);
@@ -463,7 +523,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
463523
for (const uri of Object.keys(options.sourceOverride)) {
464524
const content = options.sourceOverride[uri];
465525
if (content) {
466-
const relativePath = relative(sketchPath, FileUri.fsPath(uri));
526+
const relativePath = path.relative(sketchPath, FileUri.fsPath(uri));
467527
req.getSourceOverrideMap().set(relativePath, content);
468528
}
469529
}

‎i18n/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@
152152
"serialMonitor": "Serial Monitor",
153153
"type": "Type",
154154
"unknown": "Unknown",
155-
"updateable": "Updatable"
155+
"updateable": "Updatable",
156+
"userAbort": "User abort"
156157
},
157158
"compile": {
158159
"error": "Compilation error: {0}"

0 commit comments

Comments
 (0)
Please sign in to comment.