From 66c1b9d1b3468aefc487247d3214e7e60fea4585 Mon Sep 17 00:00:00 2001 From: Jonah Iden Date: Tue, 3 Dec 2024 15:45:22 +0100 Subject: [PATCH] Remote: still use settings and configuration from the local user dir (#14548) * Remote: still use settings and configuration from the local user dir --------- Signed-off-by: Jonah Iden --- CHANGELOG.md | 4 ++ .../electron-local-ws-connection-source.ts | 5 ++ .../container-info-contribution.ts | 3 +- packages/remote/package.json | 1 + .../local-backend-services.ts | 31 +++++++++ .../port-forwarding-service.ts | 3 +- .../remote-frontend-contribution.ts | 7 +- .../remote-frontend-module.ts | 19 +++++- .../remote-user-storage-provider.ts | 64 +++++++++++++++++++ packages/remote/tsconfig.json | 3 + .../src/browser/user-storage-contribution.ts | 13 +++- 11 files changed, 145 insertions(+), 8 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/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/package.json b/packages/remote/package.json index 2a2bf887eeb8c..357f3a8e54411 100644 --- a/packages/remote/package.json +++ b/packages/remote/package.json @@ -5,6 +5,7 @@ "dependencies": { "@theia/core": "1.56.0", "@theia/filesystem": "1.56.0", + "@theia/userstorage": "1.56.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/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 599c9ef3906d3..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'; @@ -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,16 @@ export default new ContainerModule((bind, _, __, rebind) => { bind(RemotePortForwardingProvider).toDynamicValue(ctx => ServiceConnectionProvider.createLocalProxy(ctx.container, RemoteRemotePortForwardingProviderPath)).inSingletonScope(); + bind(LocalRemoteFileSytemServer).toDynamicValue(ctx => + isRemote ? + ServiceConnectionProvider.createLocalProxy(ctx.container, remoteFileSystemPath, new RemoteFileSystemProxyFactory()) : + ctx.container.get(RemoteFileSystemServer)); + bind(LocalEnvVariablesServer).toDynamicValue(ctx => + isRemote ? + ServiceConnectionProvider.createLocalProxy(ctx.container, envVariablesPath) : + ctx.container.get(EnvVariablesServer)); + bind(LocalRemoteFileSystemProvider).toSelf().inSingletonScope(); + rebind(UserStorageContribution).to(RemoteUserStorageContribution); + }); + 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..df44c233f501d --- /dev/null +++ b/packages/remote/src/electron-browser/remote-user-storage-provider.ts @@ -0,0 +1,64 @@ +// ***************************************************************************** +// 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'; +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. + */ +@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 = getCurrentPort(); + 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 => {