Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IWebSocketFactory, BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
|
||||
|
||||
constructor(
|
||||
webSocketFactory: IWebSocketFactory | null | undefined,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@ISignService signService: ISignService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super(new BrowserSocketFactory(webSocketFactory), environmentService, productService, remoteAuthorityResolverService, signService, logService);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IChannel, IServerChannel, getDelayedChannel, IPCLogger } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { connectRemoteAgentManagement, IConnectionOptions, ISocketFactory, PersistentConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
public readonly socketFactory: ISocketFactory;
|
||||
private readonly _connection: IRemoteAgentConnection | null;
|
||||
private _environment: Promise<IRemoteAgentEnvironment | null> | null;
|
||||
|
||||
constructor(
|
||||
socketFactory: ISocketFactory,
|
||||
@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@ISignService signService: ISignService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this.socketFactory = socketFactory;
|
||||
if (this._environmentService.remoteAuthority) {
|
||||
this._connection = this._register(new RemoteAgentConnection(this._environmentService.remoteAuthority, productService.commit, this.socketFactory, this._remoteAuthorityResolverService, signService, logService));
|
||||
} else {
|
||||
this._connection = null;
|
||||
}
|
||||
this._environment = null;
|
||||
}
|
||||
|
||||
getConnection(): IRemoteAgentConnection | null {
|
||||
return this._connection;
|
||||
}
|
||||
|
||||
getEnvironment(): Promise<IRemoteAgentEnvironment | null> {
|
||||
return this.getRawEnvironment().then(undefined, () => null);
|
||||
}
|
||||
|
||||
getRawEnvironment(): Promise<IRemoteAgentEnvironment | null> {
|
||||
if (!this._environment) {
|
||||
this._environment = this._withChannel(
|
||||
async (channel, connection) => {
|
||||
const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority);
|
||||
this._remoteAuthorityResolverService._setAuthorityConnectionToken(connection.remoteAuthority, env.connectionToken);
|
||||
return env;
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
return this._environment;
|
||||
}
|
||||
|
||||
scanExtensions(skipExtensions: ExtensionIdentifier[] = []): Promise<IExtensionDescription[]> {
|
||||
return this._withChannel(
|
||||
(channel, connection) => RemoteExtensionEnvironmentChannelClient.scanExtensions(channel, connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI, skipExtensions),
|
||||
[]
|
||||
).then(undefined, () => []);
|
||||
}
|
||||
|
||||
scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise<IExtensionDescription | null> {
|
||||
return this._withChannel(
|
||||
(channel, connection) => RemoteExtensionEnvironmentChannelClient.scanSingleExtension(channel, connection.remoteAuthority, isBuiltin, extensionLocation),
|
||||
null
|
||||
).then(undefined, () => null);
|
||||
}
|
||||
|
||||
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined> {
|
||||
return this._withChannel(
|
||||
channel => RemoteExtensionEnvironmentChannelClient.getDiagnosticInfo(channel, options),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
disableTelemetry(): Promise<void> {
|
||||
return this._withChannel(
|
||||
channel => RemoteExtensionEnvironmentChannelClient.disableTelemetry(channel),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
logTelemetry(eventName: string, data: ITelemetryData): Promise<void> {
|
||||
return this._withChannel(
|
||||
channel => RemoteExtensionEnvironmentChannelClient.logTelemetry(channel, eventName, data),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
flushTelemetry(): Promise<void> {
|
||||
return this._withChannel(
|
||||
channel => RemoteExtensionEnvironmentChannelClient.flushTelemetry(channel),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
private _withChannel<R>(callback: (channel: IChannel, connection: IRemoteAgentConnection) => Promise<R>, fallback: R): Promise<R> {
|
||||
const connection = this.getConnection();
|
||||
if (!connection) {
|
||||
return Promise.resolve(fallback);
|
||||
}
|
||||
return connection.withChannel('remoteextensionsenvironment', (channel) => callback(channel, connection));
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection {
|
||||
|
||||
private readonly _onReconnecting = this._register(new Emitter<void>());
|
||||
public readonly onReconnecting = this._onReconnecting.event;
|
||||
|
||||
private readonly _onDidStateChange = this._register(new Emitter<PersistentConnectionEvent>());
|
||||
public readonly onDidStateChange = this._onDidStateChange.event;
|
||||
|
||||
readonly remoteAuthority: string;
|
||||
private _connection: Promise<Client<RemoteAgentConnectionContext>> | null;
|
||||
|
||||
constructor(
|
||||
remoteAuthority: string,
|
||||
private readonly _commit: string | undefined,
|
||||
private readonly _socketFactory: ISocketFactory,
|
||||
private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
private readonly _signService: ISignService,
|
||||
private readonly _logService: ILogService
|
||||
) {
|
||||
super();
|
||||
this.remoteAuthority = remoteAuthority;
|
||||
this._connection = null;
|
||||
}
|
||||
|
||||
getChannel<T extends IChannel>(channelName: string): T {
|
||||
return <T>getDelayedChannel(this._getOrCreateConnection().then(c => c.getChannel(channelName)));
|
||||
}
|
||||
|
||||
withChannel<T extends IChannel, R>(channelName: string, callback: (channel: T) => Promise<R>): Promise<R> {
|
||||
const channel = this.getChannel<T>(channelName);
|
||||
const result = callback(channel);
|
||||
return result;
|
||||
}
|
||||
|
||||
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void {
|
||||
this._getOrCreateConnection().then(client => client.registerChannel(channelName, channel));
|
||||
}
|
||||
|
||||
private _getOrCreateConnection(): Promise<Client<RemoteAgentConnectionContext>> {
|
||||
if (!this._connection) {
|
||||
this._connection = this._createConnection();
|
||||
}
|
||||
return this._connection;
|
||||
}
|
||||
|
||||
private async _createConnection(): Promise<Client<RemoteAgentConnectionContext>> {
|
||||
let firstCall = true;
|
||||
const options: IConnectionOptions = {
|
||||
commit: this._commit,
|
||||
socketFactory: this._socketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
if (firstCall) {
|
||||
firstCall = false;
|
||||
} else {
|
||||
this._onReconnecting.fire(undefined);
|
||||
}
|
||||
const { authority } = await this._remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority);
|
||||
return { host: authority.host, port: authority.port, connectionToken: authority.connectionToken };
|
||||
}
|
||||
},
|
||||
signService: this._signService,
|
||||
logService: this._logService,
|
||||
ipcLogger: false ? new IPCLogger(`Local \u2192 Remote`, `Remote \u2192 Local`) : null
|
||||
};
|
||||
const connection = this._register(await connectRemoteAgentManagement(options, this.remoteAuthority, `renderer`));
|
||||
this._register(connection.onDidStateChange(e => this._onDidStateChange.fire(e)));
|
||||
return connection.client;
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteConnectionFailureNotificationContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
) {
|
||||
// Let's cover the case where connecting to fetch the remote extension info fails
|
||||
remoteAgentService.getRawEnvironment()
|
||||
.then(undefined, err => {
|
||||
if (!RemoteAuthorityResolverError.isHandled(err)) {
|
||||
notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host server (Error: {0})", err ? err.message : ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(RemoteConnectionFailureNotificationContribution, LifecyclePhase.Ready);
|
||||
@@ -0,0 +1,114 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export interface IGetEnvironmentDataArguments {
|
||||
remoteAuthority: string;
|
||||
}
|
||||
|
||||
export interface IScanExtensionsArguments {
|
||||
language: string;
|
||||
remoteAuthority: string;
|
||||
extensionDevelopmentPath: UriComponents[] | undefined;
|
||||
skipExtensions: ExtensionIdentifier[];
|
||||
}
|
||||
|
||||
export interface IScanSingleExtensionArguments {
|
||||
language: string;
|
||||
remoteAuthority: string;
|
||||
isBuiltin: boolean;
|
||||
extensionLocation: UriComponents;
|
||||
}
|
||||
|
||||
export interface IRemoteAgentEnvironmentDTO {
|
||||
pid: number;
|
||||
connectionToken: string;
|
||||
appRoot: UriComponents;
|
||||
settingsPath: UriComponents;
|
||||
logsPath: UriComponents;
|
||||
extensionsPath: UriComponents;
|
||||
extensionHostLogsPath: UriComponents;
|
||||
globalStorageHome: UriComponents;
|
||||
workspaceStorageHome: UriComponents;
|
||||
userHome: UriComponents;
|
||||
os: platform.OperatingSystem;
|
||||
}
|
||||
|
||||
export class RemoteExtensionEnvironmentChannelClient {
|
||||
|
||||
static async getEnvironmentData(channel: IChannel, remoteAuthority: string): Promise<IRemoteAgentEnvironment> {
|
||||
const args: IGetEnvironmentDataArguments = {
|
||||
remoteAuthority
|
||||
};
|
||||
|
||||
const data = await channel.call<IRemoteAgentEnvironmentDTO>('getEnvironmentData', args);
|
||||
|
||||
return {
|
||||
pid: data.pid,
|
||||
connectionToken: data.connectionToken,
|
||||
appRoot: URI.revive(data.appRoot),
|
||||
settingsPath: URI.revive(data.settingsPath),
|
||||
logsPath: URI.revive(data.logsPath),
|
||||
extensionsPath: URI.revive(data.extensionsPath),
|
||||
extensionHostLogsPath: URI.revive(data.extensionHostLogsPath),
|
||||
globalStorageHome: URI.revive(data.globalStorageHome),
|
||||
workspaceStorageHome: URI.revive(data.workspaceStorageHome),
|
||||
userHome: URI.revive(data.userHome),
|
||||
os: data.os
|
||||
};
|
||||
}
|
||||
|
||||
static async scanExtensions(channel: IChannel, remoteAuthority: string, extensionDevelopmentPath: URI[] | undefined, skipExtensions: ExtensionIdentifier[]): Promise<IExtensionDescription[]> {
|
||||
const args: IScanExtensionsArguments = {
|
||||
language: platform.language,
|
||||
remoteAuthority,
|
||||
extensionDevelopmentPath,
|
||||
skipExtensions
|
||||
};
|
||||
|
||||
const extensions = await channel.call<IExtensionDescription[]>('scanExtensions', args);
|
||||
extensions.forEach(ext => { (<any>ext).extensionLocation = URI.revive(ext.extensionLocation); });
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
static async scanSingleExtension(channel: IChannel, remoteAuthority: string, isBuiltin: boolean, extensionLocation: URI): Promise<IExtensionDescription | null> {
|
||||
const args: IScanSingleExtensionArguments = {
|
||||
language: platform.language,
|
||||
remoteAuthority,
|
||||
isBuiltin,
|
||||
extensionLocation
|
||||
};
|
||||
|
||||
const extension = await channel.call<IExtensionDescription | null>('scanSingleExtension', args);
|
||||
if (extension) {
|
||||
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
static getDiagnosticInfo(channel: IChannel, options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo> {
|
||||
return channel.call<IDiagnosticInfo>('getDiagnosticInfo', options);
|
||||
}
|
||||
|
||||
static disableTelemetry(channel: IChannel): Promise<void> {
|
||||
return channel.call<void>('disableTelemetry');
|
||||
}
|
||||
|
||||
static logTelemetry(channel: IChannel, eventName: string, data: ITelemetryData): Promise<void> {
|
||||
return channel.call<void>('logTelemetry', { eventName, data });
|
||||
}
|
||||
|
||||
static flushTelemetry(channel: IChannel): Promise<void> {
|
||||
return channel.call<void>('flushTelemetry');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IStat, IWatchOptions, FileOpenOptions, IFileSystemProviderWithFileReadWriteCapability, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileFolderCopyCapability, FileReadStreamOptions, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem';
|
||||
|
||||
export interface IFileChangeDto {
|
||||
resource: UriComponents;
|
||||
type: FileChangeType;
|
||||
}
|
||||
|
||||
export class RemoteFileSystemProvider extends Disposable implements
|
||||
IFileSystemProviderWithFileReadWriteCapability,
|
||||
IFileSystemProviderWithOpenReadWriteCloseCapability,
|
||||
IFileSystemProviderWithFileReadStreamCapability,
|
||||
IFileSystemProviderWithFileFolderCopyCapability {
|
||||
|
||||
private readonly session: string = generateUuid();
|
||||
private readonly channel: IChannel;
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<readonly IFileChange[]>());
|
||||
readonly onDidChangeFile = this._onDidChange.event;
|
||||
|
||||
private _onDidWatchErrorOccur = this._register(new Emitter<string>());
|
||||
readonly onDidErrorOccur = this._onDidWatchErrorOccur.event;
|
||||
|
||||
private readonly _onDidChangeCapabilities = this._register(new Emitter<void>());
|
||||
readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event;
|
||||
|
||||
private _capabilities!: FileSystemProviderCapabilities;
|
||||
get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }
|
||||
|
||||
constructor(remoteAgentService: IRemoteAgentService) {
|
||||
super();
|
||||
|
||||
const connection = remoteAgentService.getConnection()!;
|
||||
this.channel = connection.getChannel<IChannel>(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
|
||||
|
||||
// Initially assume case sensitivity until remote environment is resolved
|
||||
this.setCaseSensitive(true);
|
||||
(async () => {
|
||||
const remoteAgentEnvironment = await remoteAgentService.getEnvironment();
|
||||
this.setCaseSensitive(remoteAgentEnvironment?.os === OperatingSystem.Linux);
|
||||
})();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.channel.listen<IFileChangeDto[] | string>('filechange', [this.session])(eventsOrError => {
|
||||
if (Array.isArray(eventsOrError)) {
|
||||
const events = eventsOrError;
|
||||
this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type })));
|
||||
} else {
|
||||
const error = eventsOrError;
|
||||
this._onDidWatchErrorOccur.fire(error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
setCaseSensitive(isCaseSensitive: boolean) {
|
||||
let capabilities = (
|
||||
FileSystemProviderCapabilities.FileReadWrite
|
||||
| FileSystemProviderCapabilities.FileOpenReadWriteClose
|
||||
| FileSystemProviderCapabilities.FileReadStream
|
||||
| FileSystemProviderCapabilities.FileFolderCopy
|
||||
);
|
||||
|
||||
if (isCaseSensitive) {
|
||||
capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
|
||||
}
|
||||
|
||||
this._capabilities = capabilities;
|
||||
this._onDidChangeCapabilities.fire(undefined);
|
||||
}
|
||||
|
||||
// --- forwarding calls
|
||||
|
||||
stat(resource: URI): Promise<IStat> {
|
||||
return this.channel.call('stat', [resource]);
|
||||
}
|
||||
|
||||
open(resource: URI, opts: FileOpenOptions): Promise<number> {
|
||||
return this.channel.call('open', [resource, opts]);
|
||||
}
|
||||
|
||||
close(fd: number): Promise<void> {
|
||||
return this.channel.call('close', [fd]);
|
||||
}
|
||||
|
||||
async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
|
||||
const [bytes, bytesRead]: [VSBuffer, number] = await this.channel.call('read', [fd, pos, length]);
|
||||
|
||||
// copy back the data that was written into the buffer on the remote
|
||||
// side. we need to do this because buffers are not referenced by
|
||||
// pointer, but only by value and as such cannot be directly written
|
||||
// to from the other process.
|
||||
data.set(bytes.buffer.slice(0, bytesRead), offset);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
async readFile(resource: URI): Promise<Uint8Array> {
|
||||
const buff = <VSBuffer>await this.channel.call('readFile', [resource]);
|
||||
|
||||
return buff.buffer;
|
||||
}
|
||||
|
||||
readFileStream(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
|
||||
const stream = newWriteableStream<Uint8Array>(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer);
|
||||
|
||||
// Reading as file stream goes through an event to the remote side
|
||||
const listener = this.channel.listen<ReadableStreamEventPayload<VSBuffer>>('readFileStream', [resource, opts])(dataOrErrorOrEnd => {
|
||||
|
||||
// data
|
||||
if (dataOrErrorOrEnd instanceof VSBuffer) {
|
||||
stream.write(dataOrErrorOrEnd.buffer);
|
||||
}
|
||||
|
||||
// end or error
|
||||
else {
|
||||
if (dataOrErrorOrEnd === 'end') {
|
||||
stream.end();
|
||||
} else {
|
||||
|
||||
// Since we receive data through a IPC channel, it is likely
|
||||
// that the error was not serialized, or only partially. To
|
||||
// ensure our API use is correct, we convert the data to an
|
||||
// error here to forward it properly.
|
||||
let error = dataOrErrorOrEnd;
|
||||
if (!(error instanceof Error)) {
|
||||
error = new Error(toErrorMessage(error));
|
||||
}
|
||||
|
||||
stream.end(error);
|
||||
}
|
||||
|
||||
// Signal to the remote side that we no longer listen
|
||||
listener.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
// Support cancellation
|
||||
token.onCancellationRequested(() => {
|
||||
|
||||
// Ensure to end the stream properly with an error
|
||||
// to indicate the cancellation.
|
||||
stream.end(canceled());
|
||||
|
||||
// Ensure to dispose the listener upon cancellation. This will
|
||||
// bubble through the remote side as event and allows to stop
|
||||
// reading the file.
|
||||
listener.dispose();
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
|
||||
return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]);
|
||||
}
|
||||
|
||||
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
|
||||
return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]);
|
||||
}
|
||||
|
||||
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
||||
return this.channel.call('delete', [resource, opts]);
|
||||
}
|
||||
|
||||
mkdir(resource: URI): Promise<void> {
|
||||
return this.channel.call('mkdir', [resource]);
|
||||
}
|
||||
|
||||
readdir(resource: URI): Promise<[string, FileType][]> {
|
||||
return this.channel.call('readdir', [resource]);
|
||||
}
|
||||
|
||||
rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.channel.call('rename', [resource, target, opts]);
|
||||
}
|
||||
|
||||
copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||
return this.channel.call('copy', [resource, target, opts]);
|
||||
}
|
||||
|
||||
watch(resource: URI, opts: IWatchOptions): IDisposable {
|
||||
const req = Math.random();
|
||||
this.channel.call('watch', [this.session, req, resource, opts]);
|
||||
|
||||
return toDisposable(() => this.channel.call('unwatch', [this.session, req]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const RemoteExtensionLogFileName = 'remoteagent';
|
||||
|
||||
export const IRemoteAgentService = createDecorator<IRemoteAgentService>('remoteAgentService');
|
||||
|
||||
export interface IRemoteAgentService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly socketFactory: ISocketFactory;
|
||||
|
||||
getConnection(): IRemoteAgentConnection | null;
|
||||
/**
|
||||
* Get the remote environment. In case of an error, returns `null`.
|
||||
*/
|
||||
getEnvironment(): Promise<IRemoteAgentEnvironment | null>;
|
||||
/**
|
||||
* Get the remote environment. Can return an error.
|
||||
*/
|
||||
getRawEnvironment(): Promise<IRemoteAgentEnvironment | null>;
|
||||
/**
|
||||
* Scan remote extensions.
|
||||
*/
|
||||
scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise<IExtensionDescription[]>;
|
||||
/**
|
||||
* Scan a single remote extension.
|
||||
*/
|
||||
scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise<IExtensionDescription | null>;
|
||||
getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise<IDiagnosticInfo | undefined>;
|
||||
disableTelemetry(): Promise<void>;
|
||||
logTelemetry(eventName: string, data?: ITelemetryData): Promise<void>;
|
||||
flushTelemetry(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IRemoteAgentConnection {
|
||||
readonly remoteAuthority: string;
|
||||
|
||||
readonly onReconnecting: Event<void>;
|
||||
readonly onDidStateChange: Event<PersistentConnectionEvent>;
|
||||
|
||||
getChannel<T extends IChannel>(channelName: string): T;
|
||||
withChannel<T extends IChannel, R>(channelName: string, callback: (channel: T) => Promise<R>): Promise<R>;
|
||||
registerChannel<T extends IServerChannel<RemoteAgentConnectionContext>>(channelName: string, channel: T): void;
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { isLocalhost, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IEditableData } from 'vs/workbench/common/views';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TunnelInformation, TunnelDescription, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
|
||||
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
|
||||
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
|
||||
const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore';
|
||||
export const TUNNEL_VIEW_ID = '~remote.forwardedPorts';
|
||||
|
||||
export enum TunnelType {
|
||||
Candidate = 'Candidate',
|
||||
Detected = 'Detected',
|
||||
Forwarded = 'Forwarded',
|
||||
Add = 'Add'
|
||||
}
|
||||
|
||||
export interface ITunnelItem {
|
||||
tunnelType: TunnelType;
|
||||
remoteHost: string;
|
||||
remotePort: number;
|
||||
localAddress?: string;
|
||||
localPort?: number;
|
||||
name?: string;
|
||||
closeable?: boolean;
|
||||
description?: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface Tunnel {
|
||||
remoteHost: string;
|
||||
remotePort: number;
|
||||
localAddress: string;
|
||||
localPort?: number;
|
||||
name?: string;
|
||||
description?: string;
|
||||
closeable?: boolean;
|
||||
}
|
||||
|
||||
export function MakeAddress(host: string, port: number): string {
|
||||
return host + ':' + port;
|
||||
}
|
||||
|
||||
export function mapHasTunnel(map: Map<string, Tunnel>, host: string, port: number): boolean {
|
||||
if (!isLocalhost(host)) {
|
||||
return map.has(MakeAddress(host, port));
|
||||
}
|
||||
|
||||
const stringAddress = MakeAddress('localhost', port);
|
||||
if (map.has(stringAddress)) {
|
||||
return true;
|
||||
}
|
||||
const numberAddress = MakeAddress('127.0.0.1', port);
|
||||
if (map.has(numberAddress)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function mapHasTunnelLocalhostOrAllInterfaces(map: Map<string, Tunnel>, host: string, port: number): boolean {
|
||||
if (!mapHasTunnel(map, host, port)) {
|
||||
const otherHost = host === '0.0.0.0' ? 'localhost' : (host === 'localhost' ? '0.0.0.0' : undefined);
|
||||
if (otherHost) {
|
||||
return mapHasTunnel(map, otherHost, port);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export class TunnelModel extends Disposable {
|
||||
readonly forwarded: Map<string, Tunnel>;
|
||||
readonly detected: Map<string, Tunnel>;
|
||||
private _onForwardPort: Emitter<Tunnel | void> = new Emitter();
|
||||
public onForwardPort: Event<Tunnel | void> = this._onForwardPort.event;
|
||||
private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter();
|
||||
public onClosePort: Event<{ host: string, port: number }> = this._onClosePort.event;
|
||||
private _onPortName: Emitter<{ host: string, port: number }> = new Emitter();
|
||||
public onPortName: Event<{ host: string, port: number }> = this._onPortName.event;
|
||||
private _candidates: { host: string, port: number, detail: string }[] = [];
|
||||
private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined;
|
||||
private _onCandidatesChanged: Emitter<void> = new Emitter();
|
||||
public onCandidatesChanged: Event<void> = this._onCandidatesChanged.event;
|
||||
private _candidateFilter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined;
|
||||
|
||||
constructor(
|
||||
@ITunnelService private readonly tunnelService: ITunnelService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
) {
|
||||
super();
|
||||
this.forwarded = new Map();
|
||||
this.tunnelService.tunnels.then(tunnels => {
|
||||
tunnels.forEach(tunnel => {
|
||||
if (tunnel.localAddress) {
|
||||
this.forwarded.set(MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort), {
|
||||
remotePort: tunnel.tunnelRemotePort,
|
||||
remoteHost: tunnel.tunnelRemoteHost,
|
||||
localAddress: tunnel.localAddress,
|
||||
localPort: tunnel.tunnelLocalPort
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.detected = new Map();
|
||||
this._register(this.tunnelService.onTunnelOpened(tunnel => {
|
||||
const key = MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
|
||||
if ((!this.forwarded.has(key)) && tunnel.localAddress) {
|
||||
this.forwarded.set(key, {
|
||||
remoteHost: tunnel.tunnelRemoteHost,
|
||||
remotePort: tunnel.tunnelRemotePort,
|
||||
localAddress: tunnel.localAddress,
|
||||
localPort: tunnel.tunnelLocalPort,
|
||||
closeable: true
|
||||
});
|
||||
this.storeForwarded();
|
||||
}
|
||||
this._onForwardPort.fire(this.forwarded.get(key)!);
|
||||
}));
|
||||
this._register(this.tunnelService.onTunnelClosed(address => {
|
||||
const key = MakeAddress(address.host, address.port);
|
||||
if (this.forwarded.has(key)) {
|
||||
this.forwarded.delete(key);
|
||||
this.storeForwarded();
|
||||
this._onClosePort.fire(address);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async restoreForwarded() {
|
||||
if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
|
||||
const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE);
|
||||
if (tunnelsString) {
|
||||
(<Tunnel[] | undefined>JSON.parse(tunnelsString))?.forEach(tunnel => {
|
||||
this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private storeForwarded() {
|
||||
if (this.configurationService.getValue('remote.restoreForwardedPorts')) {
|
||||
this.storageService.store(TUNNELS_TO_RESTORE, JSON.stringify(Array.from(this.forwarded.values())), StorageScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<RemoteTunnel | void> {
|
||||
const key = MakeAddress(remote.host, remote.port);
|
||||
if (!this.forwarded.has(key)) {
|
||||
const authority = this.environmentService.remoteAuthority;
|
||||
const addressProvider: IAddressProvider | undefined = authority ? {
|
||||
getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; }
|
||||
} : undefined;
|
||||
|
||||
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local);
|
||||
if (tunnel && tunnel.localAddress) {
|
||||
const newForward: Tunnel = {
|
||||
remoteHost: tunnel.tunnelRemoteHost,
|
||||
remotePort: tunnel.tunnelRemotePort,
|
||||
localPort: tunnel.tunnelLocalPort,
|
||||
name: name,
|
||||
closeable: true,
|
||||
localAddress: tunnel.localAddress
|
||||
};
|
||||
this.forwarded.set(key, newForward);
|
||||
this._onForwardPort.fire(newForward);
|
||||
return tunnel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name(host: string, port: number, name: string) {
|
||||
const key = MakeAddress(host, port);
|
||||
if (this.forwarded.has(key)) {
|
||||
this.forwarded.get(key)!.name = name;
|
||||
this.storeForwarded();
|
||||
this._onPortName.fire({ host, port });
|
||||
} else if (this.detected.has(key)) {
|
||||
this.detected.get(key)!.name = name;
|
||||
this._onPortName.fire({ host, port });
|
||||
}
|
||||
}
|
||||
|
||||
async close(host: string, port: number): Promise<void> {
|
||||
return this.tunnelService.closeTunnel(host, port);
|
||||
}
|
||||
|
||||
address(host: string, port: number): string | undefined {
|
||||
const key = MakeAddress(host, port);
|
||||
return (this.forwarded.get(key) || this.detected.get(key))?.localAddress;
|
||||
}
|
||||
|
||||
addEnvironmentTunnels(tunnels: TunnelDescription[]): void {
|
||||
tunnels.forEach(tunnel => {
|
||||
this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), {
|
||||
remoteHost: tunnel.remoteAddress.host,
|
||||
remotePort: tunnel.remoteAddress.port,
|
||||
localAddress: typeof tunnel.localAddress === 'string' ? tunnel.localAddress : MakeAddress(tunnel.localAddress.host, tunnel.localAddress.port),
|
||||
closeable: false
|
||||
});
|
||||
});
|
||||
this._onForwardPort.fire();
|
||||
}
|
||||
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
|
||||
this._candidateFinder = finder;
|
||||
this._onCandidatesChanged.fire();
|
||||
}
|
||||
|
||||
setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): void {
|
||||
this._candidateFilter = filter;
|
||||
}
|
||||
|
||||
get candidates(): Promise<{ host: string, port: number, detail: string }[]> {
|
||||
return this.updateCandidates().then(() => this._candidates);
|
||||
}
|
||||
|
||||
private async updateCandidates(): Promise<void> {
|
||||
if (this._candidateFinder) {
|
||||
let candidates = await this._candidateFinder();
|
||||
if (this._candidateFilter && (candidates.length > 0)) {
|
||||
candidates = await this._candidateFilter(candidates);
|
||||
}
|
||||
this._candidates = candidates.map(value => {
|
||||
const nullIndex = value.detail.indexOf('\0');
|
||||
const detail = value.detail.substr(0, nullIndex > 0 ? nullIndex : value.detail.length).trim();
|
||||
return {
|
||||
host: value.host,
|
||||
port: value.port,
|
||||
detail
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
await this.updateCandidates();
|
||||
this._onCandidatesChanged.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IRemoteExplorerService {
|
||||
readonly _serviceBrand: undefined;
|
||||
onDidChangeTargetType: Event<string[]>;
|
||||
targetType: string[];
|
||||
readonly tunnelModel: TunnelModel;
|
||||
onDidChangeEditable: Event<ITunnelItem | undefined>;
|
||||
setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void;
|
||||
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined;
|
||||
forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise<RemoteTunnel | void>;
|
||||
close(remote: { host: string, port: number }): Promise<void>;
|
||||
setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void;
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void;
|
||||
setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): IDisposable;
|
||||
refresh(): Promise<void>;
|
||||
restore(): Promise<void>;
|
||||
}
|
||||
|
||||
class RemoteExplorerService implements IRemoteExplorerService {
|
||||
public _serviceBrand: undefined;
|
||||
private _targetType: string[] = [];
|
||||
private readonly _onDidChangeTargetType: Emitter<string[]> = new Emitter<string[]>();
|
||||
public readonly onDidChangeTargetType: Event<string[]> = this._onDidChangeTargetType.event;
|
||||
private _tunnelModel: TunnelModel;
|
||||
private _editable: { tunnelItem: ITunnelItem | undefined, data: IEditableData } | undefined;
|
||||
private readonly _onDidChangeEditable: Emitter<ITunnelItem | undefined> = new Emitter();
|
||||
public readonly onDidChangeEditable: Event<ITunnelItem | undefined> = this._onDidChangeEditable.event;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@ITunnelService tunnelService: ITunnelService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
) {
|
||||
this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService, environmentService, remoteAuthorityResolverService);
|
||||
}
|
||||
|
||||
set targetType(name: string[]) {
|
||||
// Can just compare the first element of the array since there are no target overlaps
|
||||
const current: string = this._targetType.length > 0 ? this._targetType[0] : '';
|
||||
const newName: string = name.length > 0 ? name[0] : '';
|
||||
if (current !== newName) {
|
||||
this._targetType = name;
|
||||
this.storageService.store(REMOTE_EXPLORER_TYPE_KEY, this._targetType.toString(), StorageScope.WORKSPACE);
|
||||
this.storageService.store(REMOTE_EXPLORER_TYPE_KEY, this._targetType.toString(), StorageScope.GLOBAL);
|
||||
this._onDidChangeTargetType.fire(this._targetType);
|
||||
}
|
||||
}
|
||||
get targetType(): string[] {
|
||||
return this._targetType;
|
||||
}
|
||||
|
||||
get tunnelModel(): TunnelModel {
|
||||
return this._tunnelModel;
|
||||
}
|
||||
|
||||
forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<RemoteTunnel | void> {
|
||||
return this.tunnelModel.forward(remote, local, name);
|
||||
}
|
||||
|
||||
close(remote: { host: string, port: number }): Promise<void> {
|
||||
return this.tunnelModel.close(remote.host, remote.port);
|
||||
}
|
||||
|
||||
setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void {
|
||||
if (tunnelInformation && tunnelInformation.environmentTunnels) {
|
||||
this.tunnelModel.addEnvironmentTunnels(tunnelInformation.environmentTunnels);
|
||||
}
|
||||
}
|
||||
|
||||
setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void {
|
||||
if (!data) {
|
||||
this._editable = undefined;
|
||||
} else {
|
||||
this._editable = { tunnelItem, data };
|
||||
}
|
||||
this._onDidChangeEditable.fire(tunnelItem);
|
||||
}
|
||||
|
||||
getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined {
|
||||
return (this._editable &&
|
||||
((!tunnelItem && (tunnelItem === this._editable.tunnelItem)) ||
|
||||
(tunnelItem && (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost)))) ?
|
||||
this._editable.data : undefined;
|
||||
}
|
||||
|
||||
registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void {
|
||||
this.tunnelModel.registerCandidateFinder(finder);
|
||||
}
|
||||
|
||||
setCandidateFilter(filter: (candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>): IDisposable {
|
||||
if (!filter) {
|
||||
return {
|
||||
dispose: () => { }
|
||||
};
|
||||
}
|
||||
this.tunnelModel.setCandidateFilter(filter);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.tunnelModel.setCandidateFilter(undefined);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
refresh(): Promise<void> {
|
||||
return this.tunnelModel.refresh();
|
||||
}
|
||||
|
||||
restore(): Promise<void> {
|
||||
return this.tunnelModel.restoreForwarded();
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IRemoteExplorerService, RemoteExplorerService, true);
|
||||
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
|
||||
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService {
|
||||
constructor(
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@ISignService signService: ISignService,
|
||||
@ILogService logService: ILogService,
|
||||
) {
|
||||
super(nodeSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user