mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-27 05:06:42 +00:00
fix: warn user when IDE cannot save the sketch
Happens when the IDE2 backend process crashes, and the communication drops between the client and the server. Closes #2081 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
d1fa6d4f3b
commit
8f8b46fa6f
@ -68,6 +68,7 @@ import { MainMenuManager } from '../../common/main-menu-manager';
|
|||||||
import { ConfigServiceClient } from '../config/config-service-client';
|
import { ConfigServiceClient } from '../config/config-service-client';
|
||||||
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
||||||
import { DialogService } from '../dialog-service';
|
import { DialogService } from '../dialog-service';
|
||||||
|
import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
@ -172,6 +173,9 @@ export abstract class SketchContribution extends Contribution {
|
|||||||
@inject(EnvVariablesServer)
|
@inject(EnvVariablesServer)
|
||||||
protected readonly envVariableServer: EnvVariablesServer;
|
protected readonly envVariableServer: EnvVariablesServer;
|
||||||
|
|
||||||
|
@inject(ApplicationConnectionStatusContribution)
|
||||||
|
protected readonly connectionStatusService: ApplicationConnectionStatusContribution;
|
||||||
|
|
||||||
protected async sourceOverride(): Promise<Record<string, string>> {
|
protected async sourceOverride(): Promise<Record<string, string>> {
|
||||||
const override: Record<string, string> = {};
|
const override: Record<string, string> = {};
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
RenameCloudSketch,
|
RenameCloudSketch,
|
||||||
RenameCloudSketchParams,
|
RenameCloudSketchParams,
|
||||||
} from './rename-cloud-sketch';
|
} from './rename-cloud-sketch';
|
||||||
|
import { assertConnectedToBackend } from './save-sketch';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SaveAsSketch extends CloudSketchContribution {
|
export class SaveAsSketch extends CloudSketchContribution {
|
||||||
@ -64,6 +65,10 @@ export class SaveAsSketch extends CloudSketchContribution {
|
|||||||
markAsRecentlyOpened,
|
markAsRecentlyOpened,
|
||||||
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
assertConnectedToBackend({
|
||||||
|
connectionStatusService: this.connectionStatusService,
|
||||||
|
messageService: this.messageService,
|
||||||
|
});
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
if (!CurrentSketch.isValid(sketch)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
import { injectable } from '@theia/core/shared/inversify';
|
|
||||||
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
import { CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||||
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
|
import { nls } from '@theia/core/lib/common/nls';
|
||||||
|
import { injectable } from '@theia/core/shared/inversify';
|
||||||
import { ArduinoMenus } from '../menu/arduino-menus';
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
import { SaveAsSketch } from './save-as-sketch';
|
import { CurrentSketch } from '../sketches-service-client-impl';
|
||||||
|
import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service';
|
||||||
import {
|
import {
|
||||||
SketchContribution,
|
|
||||||
Command,
|
Command,
|
||||||
CommandRegistry,
|
CommandRegistry,
|
||||||
MenuModelRegistry,
|
|
||||||
KeybindingRegistry,
|
KeybindingRegistry,
|
||||||
|
MenuModelRegistry,
|
||||||
|
SketchContribution,
|
||||||
} from './contribution';
|
} from './contribution';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { SaveAsSketch } from './save-as-sketch';
|
||||||
import { CurrentSketch } from '../sketches-service-client-impl';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SaveSketch extends SketchContribution {
|
export class SaveSketch extends SketchContribution {
|
||||||
@ -36,6 +38,10 @@ export class SaveSketch extends SketchContribution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveSketch(): Promise<void> {
|
async saveSketch(): Promise<void> {
|
||||||
|
assertConnectedToBackend({
|
||||||
|
connectionStatusService: this.connectionStatusService,
|
||||||
|
messageService: this.messageService,
|
||||||
|
});
|
||||||
const sketch = await this.sketchServiceClient.currentSketch();
|
const sketch = await this.sketchServiceClient.currentSketch();
|
||||||
if (!CurrentSketch.isValid(sketch)) {
|
if (!CurrentSketch.isValid(sketch)) {
|
||||||
return;
|
return;
|
||||||
@ -63,3 +69,18 @@ export namespace SaveSketch {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/arduino/arduino-ide/issues/2081
|
||||||
|
export function assertConnectedToBackend(param: {
|
||||||
|
connectionStatusService: ApplicationConnectionStatusContribution;
|
||||||
|
messageService: MessageService;
|
||||||
|
}): void {
|
||||||
|
if (param.connectionStatusService.offlineStatus === 'backend') {
|
||||||
|
const message = nls.localize(
|
||||||
|
'theia/core/couldNotSave',
|
||||||
|
'Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.'
|
||||||
|
);
|
||||||
|
param.messageService.error(message);
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||||
} from '@theia/core/lib/browser/connection-status-service';
|
} from '@theia/core/lib/browser/connection-status-service';
|
||||||
import type { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
import type { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/index';
|
||||||
import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
|
import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||||
@ -16,11 +17,11 @@ import {
|
|||||||
postConstruct,
|
postConstruct,
|
||||||
} from '@theia/core/shared/inversify';
|
} from '@theia/core/shared/inversify';
|
||||||
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
import { ArduinoDaemon } from '../../../common/protocol';
|
import { ArduinoDaemon } from '../../../common/protocol';
|
||||||
import { assertUnreachable } from '../../../common/utils';
|
import { assertUnreachable } from '../../../common/utils';
|
||||||
import { CreateFeatures } from '../../create/create-features';
|
import { CreateFeatures } from '../../create/create-features';
|
||||||
import { NotificationCenter } from '../../notification-center';
|
import { NotificationCenter } from '../../notification-center';
|
||||||
import debounce from 'lodash.debounce';
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class IsOnline implements FrontendApplicationContribution {
|
export class IsOnline implements FrontendApplicationContribution {
|
||||||
@ -113,6 +114,8 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
|||||||
private readonly daemonPort: DaemonPort;
|
private readonly daemonPort: DaemonPort;
|
||||||
@inject(IsOnline)
|
@inject(IsOnline)
|
||||||
private readonly isOnline: IsOnline;
|
private readonly isOnline: IsOnline;
|
||||||
|
@inject(WebSocketConnectionProvider)
|
||||||
|
private readonly connectionProvider: WebSocketConnectionProvider;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected override async init(): Promise<void> {
|
protected override async init(): Promise<void> {
|
||||||
@ -125,6 +128,10 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override async performPingRequest(): Promise<void> {
|
protected override async performPingRequest(): Promise<void> {
|
||||||
|
if (!this.connectionProvider['socket'].connected) {
|
||||||
|
this.updateStatus(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.pingService.ping();
|
await this.pingService.ping();
|
||||||
this.updateStatus(this.isOnline.online);
|
this.updateStatus(this.isOnline.online);
|
||||||
@ -164,6 +171,8 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
|||||||
private readonly notificationManager: NotificationManager;
|
private readonly notificationManager: NotificationManager;
|
||||||
@inject(CreateFeatures)
|
@inject(CreateFeatures)
|
||||||
private readonly createFeatures: CreateFeatures;
|
private readonly createFeatures: CreateFeatures;
|
||||||
|
@inject(WebSocketConnectionProvider)
|
||||||
|
private readonly connectionProvider: WebSocketConnectionProvider;
|
||||||
|
|
||||||
private readonly offlineStatusDidChangeEmitter = new Emitter<
|
private readonly offlineStatusDidChangeEmitter = new Emitter<
|
||||||
OfflineConnectionStatus | undefined
|
OfflineConnectionStatus | undefined
|
||||||
@ -190,9 +199,10 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override handleOffline(): void {
|
protected override handleOffline(): void {
|
||||||
const params = {
|
const params = <OfflineMessageParams>{
|
||||||
port: this.daemonPort.port,
|
port: this.daemonPort.port,
|
||||||
online: this.isOnline.online,
|
online: this.isOnline.online,
|
||||||
|
backendConnected: this.connectionProvider['socket'].connected, // https://github.com/arduino/arduino-ide/issues/2081
|
||||||
};
|
};
|
||||||
this._offlineStatus = offlineConnectionStatusType(params);
|
this._offlineStatus = offlineConnectionStatusType(params);
|
||||||
const { text, tooltip } = offlineMessage(params);
|
const { text, tooltip } = offlineMessage(params);
|
||||||
@ -248,6 +258,7 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon
|
|||||||
interface OfflineMessageParams {
|
interface OfflineMessageParams {
|
||||||
readonly port: string | undefined;
|
readonly port: string | undefined;
|
||||||
readonly online: boolean;
|
readonly online: boolean;
|
||||||
|
readonly backendConnected: boolean;
|
||||||
}
|
}
|
||||||
interface OfflineMessage {
|
interface OfflineMessage {
|
||||||
readonly text: string;
|
readonly text: string;
|
||||||
@ -272,8 +283,8 @@ export function offlineMessage(params: OfflineMessageParams): OfflineMessage {
|
|||||||
function offlineConnectionStatusType(
|
function offlineConnectionStatusType(
|
||||||
params: OfflineMessageParams
|
params: OfflineMessageParams
|
||||||
): OfflineConnectionStatus {
|
): OfflineConnectionStatus {
|
||||||
const { port, online } = params;
|
const { port, online, backendConnected } = params;
|
||||||
if (port && online) {
|
if (!backendConnected || (port && online)) {
|
||||||
return 'backend';
|
return 'backend';
|
||||||
}
|
}
|
||||||
if (!port) {
|
if (!port) {
|
||||||
|
@ -20,25 +20,41 @@ disableJSDOM();
|
|||||||
describe('connection-status-service', () => {
|
describe('connection-status-service', () => {
|
||||||
describe('offlineMessage', () => {
|
describe('offlineMessage', () => {
|
||||||
it('should warn about the offline backend if connected to both CLI daemon and Internet but offline', () => {
|
it('should warn about the offline backend if connected to both CLI daemon and Internet but offline', () => {
|
||||||
const actual = offlineMessage({ port: '50051', online: true });
|
const actual = offlineMessage({
|
||||||
|
port: '50051',
|
||||||
|
online: true,
|
||||||
|
backendConnected: false,
|
||||||
|
});
|
||||||
expect(actual.text).to.be.equal(backendOfflineText);
|
expect(actual.text).to.be.equal(backendOfflineText);
|
||||||
expect(actual.tooltip).to.be.equal(backendOfflineTooltip);
|
expect(actual.tooltip).to.be.equal(backendOfflineTooltip);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn about the offline CLI daemon if the CLI daemon port is missing but has Internet connection', () => {
|
it('should warn about the offline CLI daemon if the CLI daemon port is missing but has Internet connection', () => {
|
||||||
const actual = offlineMessage({ port: undefined, online: true });
|
const actual = offlineMessage({
|
||||||
|
port: undefined,
|
||||||
|
online: true,
|
||||||
|
backendConnected: true,
|
||||||
|
});
|
||||||
expect(actual.text.endsWith(daemonOfflineText)).to.be.true;
|
expect(actual.text.endsWith(daemonOfflineText)).to.be.true;
|
||||||
expect(actual.tooltip).to.be.equal(daemonOfflineTooltip);
|
expect(actual.tooltip).to.be.equal(daemonOfflineTooltip);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn about the offline CLI daemon if the CLI daemon port is missing and has no Internet connection', () => {
|
it('should warn about the offline CLI daemon if the CLI daemon port is missing and has no Internet connection', () => {
|
||||||
const actual = offlineMessage({ port: undefined, online: false });
|
const actual = offlineMessage({
|
||||||
|
port: undefined,
|
||||||
|
online: false,
|
||||||
|
backendConnected: true,
|
||||||
|
});
|
||||||
expect(actual.text.endsWith(daemonOfflineText)).to.be.true;
|
expect(actual.text.endsWith(daemonOfflineText)).to.be.true;
|
||||||
expect(actual.tooltip).to.be.equal(daemonOfflineTooltip);
|
expect(actual.tooltip).to.be.equal(daemonOfflineTooltip);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn about no Internet connection if CLI daemon port is available but the Internet connection is offline', () => {
|
it('should warn about no Internet connection if CLI daemon port is available but the Internet connection is offline', () => {
|
||||||
const actual = offlineMessage({ port: '50051', online: false });
|
const actual = offlineMessage({
|
||||||
|
port: '50051',
|
||||||
|
online: false,
|
||||||
|
backendConnected: true,
|
||||||
|
});
|
||||||
expect(actual.text.endsWith(offlineText)).to.be.true;
|
expect(actual.text.endsWith(offlineText)).to.be.true;
|
||||||
expect(actual.tooltip).to.be.equal(offlineTooltip);
|
expect(actual.tooltip).to.be.equal(offlineTooltip);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user