From cf0fb9a6c2e6b9e04598950cca7ca8bc31c56b5a Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Thu, 28 Nov 2024 10:38:16 +0100 Subject: [PATCH 1/2] Remote: still use settings and configuration from the local user dir Signed-off-by: Jonah Iden --- packages/remote/package.json | 1 + .../local-backend-services.ts | 31 +++++++++ .../remote-frontend-module.ts | 20 ++++++ .../remote-user-storage-provider.ts | 63 +++++++++++++++++++ packages/remote/tsconfig.json | 3 + .../src/browser/user-storage-contribution.ts | 13 +++- 6 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 packages/remote/src/electron-browser/local-backend-services.ts create mode 100644 packages/remote/src/electron-browser/remote-user-storage-provider.ts diff --git a/packages/remote/package.json b/packages/remote/package.json index a73ecfc6bd16a..cc47c3d283f14 100644 --- a/packages/remote/package.json +++ b/packages/remote/package.json @@ -5,6 +5,7 @@ "dependencies": { "@theia/core": "1.55.0", "@theia/filesystem": "1.55.0", + "@theia/userstorage": "1.55.0", "archiver": "^5.3.1", "decompress": "^4.2.1", "decompress-tar": "^4.0.0", diff --git a/packages/remote/src/electron-browser/local-backend-services.ts b/packages/remote/src/electron-browser/local-backend-services.ts new file mode 100644 index 0000000000000..61697304e2fcf --- /dev/null +++ b/packages/remote/src/electron-browser/local-backend-services.ts @@ -0,0 +1,31 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { RpcProxy } from '@theia/core'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { RemoteFileSystemProvider, RemoteFileSystemServer } from '@theia/filesystem/lib/common/remote-file-system-provider'; + +export const LocalEnvVariablesServer = Symbol('LocalEnviromentVariableServer'); +export const LocalRemoteFileSytemServer = Symbol('LocalRemoteFileSytemServer'); + +/** + * provide file access to local files while connected to a remote workspace or dev container. + */ +@injectable() +export class LocalRemoteFileSystemProvider extends RemoteFileSystemProvider { + @inject(LocalRemoteFileSytemServer) + protected override readonly server: RpcProxy; +} diff --git a/packages/remote/src/electron-browser/remote-frontend-module.ts b/packages/remote/src/electron-browser/remote-frontend-module.ts index 599c9ef3906d3..537cb498f1bd1 100644 --- a/packages/remote/src/electron-browser/remote-frontend-module.ts +++ b/packages/remote/src/electron-browser/remote-frontend-module.ts @@ -32,6 +32,11 @@ import { PortForwardingService } from './port-forwarding/port-forwarding-service import { RemotePortForwardingProvider, RemoteRemotePortForwardingProviderPath } from '../electron-common/remote-port-forwarding-provider'; import { ServiceConnectionProvider } from '@theia/core/lib/browser/messaging/service-connection-provider'; import '../../src/electron-browser/style/port-forwarding-widget.css'; +import { UserStorageContribution } from '@theia/userstorage/lib/browser/user-storage-contribution'; +import { RemoteUserStorageContribution } from './remote-user-storage-provider'; +import { remoteFileSystemPath, RemoteFileSystemProxyFactory, RemoteFileSystemServer } from '@theia/filesystem/lib/common/remote-file-system-provider'; +import { LocalEnvVariablesServer, LocalRemoteFileSystemProvider, LocalRemoteFileSytemServer } from './local-backend-services'; +import { envVariablesPath, EnvVariablesServer } from '@theia/core/lib/common/env-variables'; export default new ContainerModule((bind, _, __, rebind) => { bind(RemoteFrontendContribution).toSelf().inSingletonScope(); @@ -65,4 +70,19 @@ export default new ContainerModule((bind, _, __, rebind) => { bind(RemotePortForwardingProvider).toDynamicValue(ctx => ServiceConnectionProvider.createLocalProxy(ctx.container, RemoteRemotePortForwardingProviderPath)).inSingletonScope(); + bind(LocalRemoteFileSytemServer).toDynamicValue(ctx => + isRemoteConnection() ? + ServiceConnectionProvider.createLocalProxy(ctx.container, remoteFileSystemPath, new RemoteFileSystemProxyFactory()) : + ctx.container.get(RemoteFileSystemServer)); + bind(LocalEnvVariablesServer).toDynamicValue(ctx => + isRemoteConnection() ? + ServiceConnectionProvider.createLocalProxy(ctx.container, envVariablesPath) : + ctx.container.get(EnvVariablesServer)); + bind(LocalRemoteFileSystemProvider).toSelf().inSingletonScope(); + rebind(UserStorageContribution).to(RemoteUserStorageContribution); + }); + +function isRemoteConnection(): boolean { + return !!new URLSearchParams(window.location.search).get('localPort'); +} diff --git a/packages/remote/src/electron-browser/remote-user-storage-provider.ts b/packages/remote/src/electron-browser/remote-user-storage-provider.ts new file mode 100644 index 0000000000000..35d42245ffe76 --- /dev/null +++ b/packages/remote/src/electron-browser/remote-user-storage-provider.ts @@ -0,0 +1,63 @@ +// ***************************************************************************** +// Copyright (C) 2024 TypeFox and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 +// ***************************************************************************** + +import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'; +import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import { FileSystemProvider } from '@theia/filesystem/lib/common/files'; +import { UserStorageContribution } from '@theia/userstorage/lib/browser/user-storage-contribution'; +import { RemoteStatusService } from '../electron-common/remote-status-service'; +import { LocalEnvVariablesServer, LocalRemoteFileSystemProvider } from './local-backend-services'; +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { URI } from '@theia/core'; +import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; + +/** + * This overide is to have remote connections still use settings, keymaps, etc. from the local machine. + */ +@injectable() +export class RemoteUserStorageContribution extends UserStorageContribution { + @inject(RemoteStatusService) + protected readonly remoteStatusService: RemoteStatusService; + + @inject(LocalRemoteFileSystemProvider) + protected readonly localRemoteFileSystemProvider: LocalRemoteFileSystemProvider; + + @inject(LocalEnvVariablesServer) + protected readonly localEnvironments: EnvVariablesServer; + + isRemoteConnection: Deferred = new Deferred(); + + @postConstruct() + protected init(): void { + const port = new URLSearchParams(location.search).get('port'); + if (port) { + this.remoteStatusService.getStatus(Number(port)).then(status => this.isRemoteConnection.resolve(status.alive)); + } + } + + protected override async getDelegate(service: FileService): Promise { + return await this.isRemoteConnection.promise ? + this.localRemoteFileSystemProvider + : service.activateProvider('file'); + } + + protected override async getCongigDirUri(): Promise { + return await this.isRemoteConnection.promise ? + new URI(await this.localEnvironments.getConfigDirUri()) + : super.getCongigDirUri(); + } + +} diff --git a/packages/remote/tsconfig.json b/packages/remote/tsconfig.json index 583ad6411ef85..6ab2143003036 100644 --- a/packages/remote/tsconfig.json +++ b/packages/remote/tsconfig.json @@ -14,6 +14,9 @@ }, { "path": "../filesystem" + }, + { + "path": "../userstorage" } ] } diff --git a/packages/userstorage/src/browser/user-storage-contribution.ts b/packages/userstorage/src/browser/user-storage-contribution.ts index b988885d5681d..cc7f6971324fb 100644 --- a/packages/userstorage/src/browser/user-storage-contribution.ts +++ b/packages/userstorage/src/browser/user-storage-contribution.ts @@ -22,6 +22,7 @@ import { FileSystemProvider } from '@theia/filesystem/lib/common/files'; import { FileService, FileServiceContribution } from '@theia/filesystem/lib/browser/file-service'; import { DelegatingFileSystemProvider } from '@theia/filesystem/lib/common/delegating-file-system-provider'; import { UserStorageUri } from './user-storage-uri'; +import { MaybePromise } from '@theia/core'; @injectable() export class UserStorageContribution implements FileServiceContribution { @@ -40,9 +41,17 @@ export class UserStorageContribution implements FileServiceContribution { }); } + protected getDelegate(service: FileService): MaybePromise { + return service.activateProvider('file'); + } + + protected async getCongigDirUri(): Promise { + return new URI(await this.environments.getConfigDirUri()); + } + protected async createProvider(service: FileService): Promise { - const delegate = await service.activateProvider('file'); - const configDirUri = new URI(await this.environments.getConfigDirUri()); + const delegate = await this.getDelegate(service); + const configDirUri = await this.getCongigDirUri(); return new DelegatingFileSystemProvider(delegate, { uriConverter: { to: resource => { From a7db8321a5dbaf14e3ca58069faabc3f8ba8f435 Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Fri, 29 Nov 2024 11:13:36 +0100 Subject: [PATCH 2/2] review changes (changelog and central method for port localPort) Signed-off-by: Jonah Iden --- CHANGELOG.md | 4 ++++ .../messaging/electron-local-ws-connection-source.ts | 5 +++++ .../src/electron-browser/container-info-contribution.ts | 3 ++- .../port-forwarding/port-forwarding-service.ts | 3 ++- .../src/electron-browser/remote-frontend-contribution.ts | 7 ++++--- .../src/electron-browser/remote-frontend-module.ts | 9 +++------ .../src/electron-browser/remote-user-storage-provider.ts | 3 ++- 7 files changed, 22 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9bfb996c16bc..da78c0c18aef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - [Previous Changelogs](https://github.com/eclipse-theia/theia/tree/master/doc/changelogs/) +[Breaking Changes:](#breaking_changes_1.57.0) + +- [remote] use local settings and configuration while connected to remote (rebinds UserStorageProvider) [#14548]https://github.com/eclipse-theia/theia/pull/14548/ + ## 1.56.0 - 11/28/2024 - [ai] added support for users to specify custom request settings, model, and optionally provider-specific [#14535](https://github.com/eclipse-theia/theia/pull/14535) diff --git a/packages/core/src/electron-browser/messaging/electron-local-ws-connection-source.ts b/packages/core/src/electron-browser/messaging/electron-local-ws-connection-source.ts index f62e740a02d48..82c19a40aa598 100644 --- a/packages/core/src/electron-browser/messaging/electron-local-ws-connection-source.ts +++ b/packages/core/src/electron-browser/messaging/electron-local-ws-connection-source.ts @@ -23,6 +23,11 @@ export function getLocalPort(): string | undefined { return params.get('localPort') ?? undefined; } +export function getCurrentPort(): string | undefined { + const params = new URLSearchParams(location.search); + return params.get('port') ?? undefined; +} + @injectable() export class ElectronLocalWebSocketConnectionSource extends WebSocketConnectionSource { diff --git a/packages/dev-container/src/electron-browser/container-info-contribution.ts b/packages/dev-container/src/electron-browser/container-info-contribution.ts index 06a39417f5a6b..40532f2d7a675 100644 --- a/packages/dev-container/src/electron-browser/container-info-contribution.ts +++ b/packages/dev-container/src/electron-browser/container-info-contribution.ts @@ -19,6 +19,7 @@ import { FrontendApplicationContribution } from '@theia/core/lib/browser'; import type { ContainerInspectInfo } from 'dockerode'; import { RemoteContainerConnectionProvider } from '../electron-common/remote-container-connection-provider'; import { PortForwardingService } from '@theia/remote/lib/electron-browser/port-forwarding/port-forwarding-service'; +import { getCurrentPort } from '@theia/core/lib/electron-browser/messaging/electron-local-ws-connection-source'; @injectable() export class ContainerInfoContribution implements FrontendApplicationContribution { @@ -32,7 +33,7 @@ export class ContainerInfoContribution implements FrontendApplicationContributio containerInfo: ContainerInspectInfo | undefined; async onStart(): Promise { - this.containerInfo = await this.connectionProvider.getCurrentContainerInfo(parseInt(new URLSearchParams(location.search).get('port') ?? '0')); + this.containerInfo = await this.connectionProvider.getCurrentContainerInfo(parseInt(getCurrentPort() ?? '0')); this.portForwardingService.forwardedPorts = Object.entries(this.containerInfo?.NetworkSettings.Ports ?? {}).flatMap(([_, ports]) => ( ports.map(port => ({ diff --git a/packages/remote/src/electron-browser/port-forwarding/port-forwarding-service.ts b/packages/remote/src/electron-browser/port-forwarding/port-forwarding-service.ts index 7a5a0c58bfe33..d9506c5dd8484 100644 --- a/packages/remote/src/electron-browser/port-forwarding/port-forwarding-service.ts +++ b/packages/remote/src/electron-browser/port-forwarding/port-forwarding-service.ts @@ -17,6 +17,7 @@ import { Emitter } from '@theia/core'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { RemotePortForwardingProvider } from '../../electron-common/remote-port-forwarding-provider'; +import { getCurrentPort } from '@theia/core/lib/electron-browser/messaging/electron-local-ws-connection-source'; export interface ForwardedPort { localPort?: number; @@ -50,7 +51,7 @@ export class PortForwardingService { } updatePort(port: ForwardedPort, newAdress: string): void { - const connectionPort = new URLSearchParams(location.search).get('port'); + const connectionPort = getCurrentPort(); if (!connectionPort) { // if there is no open remote connection we can't forward a port return; diff --git a/packages/remote/src/electron-browser/remote-frontend-contribution.ts b/packages/remote/src/electron-browser/remote-frontend-contribution.ts index c0a02f3cb2249..124e558a523df 100644 --- a/packages/remote/src/electron-browser/remote-frontend-contribution.ts +++ b/packages/remote/src/electron-browser/remote-frontend-contribution.ts @@ -21,6 +21,7 @@ import { RemoteStatus, RemoteStatusService } from '../electron-common/remote-sta import { RemoteRegistry, RemoteRegistryContribution } from './remote-registry-contribution'; import { RemoteService } from './remote-service'; import { WindowService } from '@theia/core/lib/browser/window/window-service'; +import { getLocalPort, getCurrentPort } from '@theia/core/lib/electron-browser/messaging/electron-local-ws-connection-source'; export namespace RemoteCommands { export const REMOTE_SELECT: Command = { @@ -59,7 +60,7 @@ export class RemoteFrontendContribution implements CommandContribution, Frontend protected remoteRegistry = new RemoteRegistry(); async configure(): Promise { - const port = new URLSearchParams(location.search).get('port'); + const port = getCurrentPort(); if (port) { const status = await this.remoteStatusService.getStatus(Number(port)); await this.setStatusBar(status); @@ -106,9 +107,9 @@ export class RemoteFrontendContribution implements CommandContribution, Frontend } protected async disconnectRemote(): Promise { - const searchParams = new URLSearchParams(location.search); - const localPort = searchParams.get('localPort'); + const localPort = getLocalPort(); if (localPort) { + const searchParams = new URLSearchParams(location.search); const currentPort = searchParams.get('port'); this.remoteStatusService.connectionClosed(parseInt(currentPort ?? '0')); this.windowService.reload({ search: { port: localPort } }); diff --git a/packages/remote/src/electron-browser/remote-frontend-module.ts b/packages/remote/src/electron-browser/remote-frontend-module.ts index 537cb498f1bd1..59d80fddd5e9e 100644 --- a/packages/remote/src/electron-browser/remote-frontend-module.ts +++ b/packages/remote/src/electron-browser/remote-frontend-module.ts @@ -16,7 +16,7 @@ import { bindContributionProvider, CommandContribution } from '@theia/core'; import { ContainerModule } from '@theia/core/shared/inversify'; -import { bindViewContribution, FrontendApplicationContribution, WidgetFactory } from '@theia/core/lib/browser'; +import { bindViewContribution, FrontendApplicationContribution, isRemote, WidgetFactory } from '@theia/core/lib/browser'; import { RemoteSSHContribution } from './remote-ssh-contribution'; import { RemoteSSHConnectionProvider, RemoteSSHConnectionProviderPath } from '../electron-common/remote-ssh-connection-provider'; import { RemoteFrontendContribution } from './remote-frontend-contribution'; @@ -71,11 +71,11 @@ export default new ContainerModule((bind, _, __, rebind) => { ServiceConnectionProvider.createLocalProxy(ctx.container, RemoteRemotePortForwardingProviderPath)).inSingletonScope(); bind(LocalRemoteFileSytemServer).toDynamicValue(ctx => - isRemoteConnection() ? + isRemote ? ServiceConnectionProvider.createLocalProxy(ctx.container, remoteFileSystemPath, new RemoteFileSystemProxyFactory()) : ctx.container.get(RemoteFileSystemServer)); bind(LocalEnvVariablesServer).toDynamicValue(ctx => - isRemoteConnection() ? + isRemote ? ServiceConnectionProvider.createLocalProxy(ctx.container, envVariablesPath) : ctx.container.get(EnvVariablesServer)); bind(LocalRemoteFileSystemProvider).toSelf().inSingletonScope(); @@ -83,6 +83,3 @@ export default new ContainerModule((bind, _, __, rebind) => { }); -function isRemoteConnection(): boolean { - return !!new URLSearchParams(window.location.search).get('localPort'); -} diff --git a/packages/remote/src/electron-browser/remote-user-storage-provider.ts b/packages/remote/src/electron-browser/remote-user-storage-provider.ts index 35d42245ffe76..df44c233f501d 100644 --- a/packages/remote/src/electron-browser/remote-user-storage-provider.ts +++ b/packages/remote/src/electron-browser/remote-user-storage-provider.ts @@ -23,6 +23,7 @@ import { LocalEnvVariablesServer, LocalRemoteFileSystemProvider } from './local- import { Deferred } from '@theia/core/lib/common/promise-util'; import { URI } from '@theia/core'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; +import { getCurrentPort } from '@theia/core/lib/electron-browser/messaging/electron-local-ws-connection-source'; /** * This overide is to have remote connections still use settings, keymaps, etc. from the local machine. @@ -42,7 +43,7 @@ export class RemoteUserStorageContribution extends UserStorageContribution { @postConstruct() protected init(): void { - const port = new URLSearchParams(location.search).get('port'); + const port = getCurrentPort(); if (port) { this.remoteStatusService.getStatus(Number(port)).then(status => this.isRemoteConnection.resolve(status.alive)); }