diff --git a/arduino-ide-extension/src/browser/contributions/contribution.ts b/arduino-ide-extension/src/browser/contributions/contribution.ts index 085558a05..8de4d8ff4 100644 --- a/arduino-ide-extension/src/browser/contributions/contribution.ts +++ b/arduino-ide-extension/src/browser/contributions/contribution.ts @@ -68,6 +68,7 @@ import { MainMenuManager } from '../../common/main-menu-manager'; import { ConfigServiceClient } from '../config/config-service-client'; import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; import { DialogService } from '../dialog-service'; +import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service'; export { Command, @@ -172,6 +173,9 @@ export abstract class SketchContribution extends Contribution { @inject(EnvVariablesServer) protected readonly envVariableServer: EnvVariablesServer; + @inject(ApplicationConnectionStatusContribution) + protected readonly connectionStatusService: ApplicationConnectionStatusContribution; + protected async sourceOverride(): Promise> { const override: Record = {}; const sketch = await this.sketchServiceClient.currentSketch(); diff --git a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts index 876f9c872..26dd6158f 100644 --- a/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-as-sketch.ts @@ -24,6 +24,7 @@ import { RenameCloudSketch, RenameCloudSketchParams, } from './rename-cloud-sketch'; +import { assertConnectedToBackend } from './save-sketch'; @injectable() export class SaveAsSketch extends CloudSketchContribution { @@ -64,6 +65,10 @@ export class SaveAsSketch extends CloudSketchContribution { markAsRecentlyOpened, }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT ): Promise { + assertConnectedToBackend({ + connectionStatusService: this.connectionStatusService, + messageService: this.messageService, + }); const sketch = await this.sketchServiceClient.currentSketch(); if (!CurrentSketch.isValid(sketch)) { return false; diff --git a/arduino-ide-extension/src/browser/contributions/save-sketch.ts b/arduino-ide-extension/src/browser/contributions/save-sketch.ts index d05a47982..ac20d8aa6 100644 --- a/arduino-ide-extension/src/browser/contributions/save-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/save-sketch.ts @@ -1,16 +1,18 @@ -import { injectable } from '@theia/core/shared/inversify'; 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 { SaveAsSketch } from './save-as-sketch'; +import { CurrentSketch } from '../sketches-service-client-impl'; +import { ApplicationConnectionStatusContribution } from '../theia/core/connection-status-service'; import { - SketchContribution, Command, CommandRegistry, - MenuModelRegistry, KeybindingRegistry, + MenuModelRegistry, + SketchContribution, } from './contribution'; -import { nls } from '@theia/core/lib/common'; -import { CurrentSketch } from '../sketches-service-client-impl'; +import { SaveAsSketch } from './save-as-sketch'; @injectable() export class SaveSketch extends SketchContribution { @@ -36,6 +38,10 @@ export class SaveSketch extends SketchContribution { } async saveSketch(): Promise { + assertConnectedToBackend({ + connectionStatusService: this.connectionStatusService, + messageService: this.messageService, + }); const sketch = await this.sketchServiceClient.currentSketch(); if (!CurrentSketch.isValid(sketch)) { 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); + } +} diff --git a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts index 983850d0c..b38f8d084 100644 --- a/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts +++ b/arduino-ide-extension/src/browser/theia/core/connection-status-service.ts @@ -4,6 +4,7 @@ import { FrontendConnectionStatusService as TheiaFrontendConnectionStatusService, } from '@theia/core/lib/browser/connection-status-service'; 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 { Disposable } from '@theia/core/lib/common/disposable'; import { Emitter, Event } from '@theia/core/lib/common/event'; @@ -16,11 +17,11 @@ import { postConstruct, } from '@theia/core/shared/inversify'; import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager'; +import debounce from 'lodash.debounce'; import { ArduinoDaemon } from '../../../common/protocol'; import { assertUnreachable } from '../../../common/utils'; import { CreateFeatures } from '../../create/create-features'; import { NotificationCenter } from '../../notification-center'; -import debounce from 'lodash.debounce'; @injectable() export class IsOnline implements FrontendApplicationContribution { @@ -113,6 +114,8 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat private readonly daemonPort: DaemonPort; @inject(IsOnline) private readonly isOnline: IsOnline; + @inject(WebSocketConnectionProvider) + private readonly connectionProvider: WebSocketConnectionProvider; @postConstruct() protected override async init(): Promise { @@ -125,6 +128,10 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat } protected override async performPingRequest(): Promise { + if (!this.connectionProvider['socket'].connected) { + this.updateStatus(false); + return; + } try { await this.pingService.ping(); this.updateStatus(this.isOnline.online); @@ -164,6 +171,8 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon private readonly notificationManager: NotificationManager; @inject(CreateFeatures) private readonly createFeatures: CreateFeatures; + @inject(WebSocketConnectionProvider) + private readonly connectionProvider: WebSocketConnectionProvider; private readonly offlineStatusDidChangeEmitter = new Emitter< OfflineConnectionStatus | undefined @@ -190,9 +199,10 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon } protected override handleOffline(): void { - const params = { + const params = { port: this.daemonPort.port, online: this.isOnline.online, + backendConnected: this.connectionProvider['socket'].connected, // https://github.com/arduino/arduino-ide/issues/2081 }; this._offlineStatus = offlineConnectionStatusType(params); const { text, tooltip } = offlineMessage(params); @@ -248,6 +258,7 @@ export class ApplicationConnectionStatusContribution extends TheiaApplicationCon interface OfflineMessageParams { readonly port: string | undefined; readonly online: boolean; + readonly backendConnected: boolean; } interface OfflineMessage { readonly text: string; @@ -272,8 +283,8 @@ export function offlineMessage(params: OfflineMessageParams): OfflineMessage { function offlineConnectionStatusType( params: OfflineMessageParams ): OfflineConnectionStatus { - const { port, online } = params; - if (port && online) { + const { port, online, backendConnected } = params; + if (!backendConnected || (port && online)) { return 'backend'; } if (!port) { diff --git a/arduino-ide-extension/src/test/browser/connection-status-service.test.ts b/arduino-ide-extension/src/test/browser/connection-status-service.test.ts index 1f1228ce6..c84eade55 100644 --- a/arduino-ide-extension/src/test/browser/connection-status-service.test.ts +++ b/arduino-ide-extension/src/test/browser/connection-status-service.test.ts @@ -20,25 +20,41 @@ disableJSDOM(); describe('connection-status-service', () => { describe('offlineMessage', () => { 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.tooltip).to.be.equal(backendOfflineTooltip); }); 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.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', () => { - 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.tooltip).to.be.equal(daemonOfflineTooltip); }); 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.tooltip).to.be.equal(offlineTooltip); });