未验证 提交 aa063dbe 编写于 作者: A Alex Ross 提交者: GitHub
上级 69b546a3
......@@ -97,6 +97,9 @@ export const enum MenuId {
StatusBarWindowIndicatorMenu,
TouchBarContext,
TitleBarContext,
TunnelContext,
TunnelInline,
TunnelTitle,
ViewItemContext,
ViewTitle,
CommentThreadTitle,
......
......@@ -5,13 +5,14 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
export const ITunnelService = createDecorator<ITunnelService>('tunnelService');
export interface RemoteTunnel {
readonly tunnelRemotePort: number;
readonly tunnelLocalPort: number;
readonly localAddress?: URI;
dispose(): void;
}
......@@ -19,8 +20,11 @@ export interface ITunnelService {
_serviceBrand: undefined;
readonly tunnels: Promise<readonly RemoteTunnel[]>;
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<number>;
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined;
openTunnel(remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remotePort: number): Promise<void>;
}
export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined {
......
......@@ -4,13 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { Event, Emitter } from 'vs/base/common/event';
export class NoOpTunnelService implements ITunnelService {
_serviceBrand: undefined;
public readonly tunnels: Promise<readonly RemoteTunnel[]> = Promise.resolve([]);
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event;
openTunnel(_remotePort: number): Promise<RemoteTunnel> | undefined {
return undefined;
}
async closeTunnel(_remotePort: number): Promise<void> {
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.customview-tree .tunnel-view-label {
flex: 1;
}
.customview-tree .tunnel-view-label .action-label.codicon {
margin-top: 4px;
}
......@@ -44,6 +44,9 @@ import { isStringArray } from 'vs/base/common/types';
import { IRemoteExplorerService, HelpInformation } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { startsWith } from 'vs/base/common/strings';
import { TunnelPanelDescriptor, TunnelViewModel } from 'vs/workbench/contrib/remote/browser/tunnelView';
import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views';
import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet';
class HelpModel {
items: IHelpItem[] | undefined;
......@@ -263,6 +266,7 @@ class HelpAction extends Action {
export class RemoteViewlet extends FilterViewContainerViewlet {
private actions: IAction[] | undefined;
private tunnelPanelDescriptor: TunnelPanelDescriptor | undefined;
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
......@@ -274,7 +278,8 @@ export class RemoteViewlet extends FilterViewContainerViewlet {
@IThemeService themeService: IThemeService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
) {
super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
}
......@@ -308,6 +313,17 @@ export class RemoteViewlet extends FilterViewContainerViewlet {
const title = nls.localize('remote.explorer', "Remote Explorer");
return title;
}
onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] {
// Call to super MUST be first, since registering the additional view will cause this to be called again.
const panels: ViewletPane[] = super.onDidAddViews(added);
if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && this.configurationService.getValue<boolean>('remote.forwardedPortsView.visible')) {
this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService);
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViews([this.tunnelPanelDescriptor!], VIEW_CONTAINER);
}
return panels;
}
}
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create(
......
此差异已折叠。
......@@ -10,15 +10,111 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { URI } from 'vs/base/common/uri';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
export interface Tunnel {
remote: number;
localUri: URI;
local?: number;
name?: string;
description?: string;
closeable?: boolean;
}
export class TunnelModel extends Disposable {
readonly forwarded: Map<number, Tunnel>;
readonly published: Map<number, Tunnel>;
readonly candidates: Map<number, Tunnel>;
private _onForwardPort: Emitter<Tunnel> = new Emitter();
public onForwardPort: Event<Tunnel> = this._onForwardPort.event;
private _onClosePort: Emitter<number> = new Emitter();
public onClosePort: Event<number> = this._onClosePort.event;
private _onPortName: Emitter<number> = new Emitter();
public onPortName: Event<number> = this._onPortName.event;
constructor(
@ITunnelService private readonly tunnelService: ITunnelService
) {
super();
this.forwarded = new Map();
this.tunnelService.tunnels.then(tunnels => {
tunnels.forEach(tunnel => {
if (tunnel.localAddress) {
this.forwarded.set(tunnel.tunnelRemotePort, {
remote: tunnel.tunnelRemotePort,
localUri: tunnel.localAddress,
local: tunnel.tunnelLocalPort
});
}
});
});
this.published = new Map();
this.candidates = new Map();
this._register(this.tunnelService.onTunnelOpened(tunnel => {
if (this.candidates.has(tunnel.tunnelRemotePort)) {
this.candidates.delete(tunnel.tunnelRemotePort);
}
if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) {
this.forwarded.set(tunnel.tunnelRemotePort, {
remote: tunnel.tunnelRemotePort,
localUri: tunnel.localAddress,
local: tunnel.tunnelLocalPort
});
}
this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!);
}));
this._register(this.tunnelService.onTunnelClosed(remotePort => {
if (this.forwarded.has(remotePort)) {
this.forwarded.delete(remotePort);
this._onClosePort.fire(remotePort);
}
}));
}
async forward(remote: number, local?: number, name?: string): Promise<void> {
if (!this.forwarded.has(remote)) {
const tunnel = await this.tunnelService.openTunnel(remote, local);
if (tunnel && tunnel.localAddress) {
const newForward: Tunnel = {
remote: tunnel.tunnelRemotePort,
local: tunnel.tunnelLocalPort,
name: name,
closeable: true,
localUri: tunnel.localAddress
};
this.forwarded.set(remote, newForward);
this._onForwardPort.fire(newForward);
}
}
}
name(remote: number, name: string) {
if (this.forwarded.has(remote)) {
this.forwarded.get(remote)!.name = name;
this._onPortName.fire(remote);
}
}
async close(remote: number): Promise<void> {
return this.tunnelService.closeTunnel(remote);
}
address(remote: number): URI | undefined {
return (this.forwarded.get(remote) || this.published.get(remote))?.localUri;
}
}
export interface IRemoteExplorerService {
_serviceBrand: undefined;
onDidChangeTargetType: Event<string>;
targetType: string;
readonly helpInformation: HelpInformation[];
readonly tunnelModel: TunnelModel;
}
export interface HelpInformation {
......@@ -62,8 +158,12 @@ class RemoteExplorerService implements IRemoteExplorerService {
private _onDidChangeTargetType: Emitter<string> = new Emitter<string>();
public onDidChangeTargetType: Event<string> = this._onDidChangeTargetType.event;
private _helpInformation: HelpInformation[] = [];
private _tunnelModel: TunnelModel;
constructor(@IStorageService private readonly storageService: IStorageService) {
constructor(
@IStorageService private readonly storageService: IStorageService,
@ITunnelService tunnelService: ITunnelService) {
this._tunnelModel = new TunnelModel(tunnelService);
remoteHelpExtPoint.setHandler((extensions) => {
let helpInformation: HelpInformation[] = [];
for (let extension of extensions) {
......@@ -108,6 +208,10 @@ class RemoteExplorerService implements IRemoteExplorerService {
get helpInformation(): HelpInformation[] {
return this._helpInformation;
}
get tunnelModel(): TunnelModel {
return this._tunnelModel;
}
}
registerSingleton(IRemoteExplorerService, RemoteExplorerService, true);
......@@ -17,9 +17,11 @@ import { ISignService } from 'vs/platform/sign/common/sign';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { findFreePort } from 'vs/base/node/ports';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort);
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort, tunnelLocalPort);
return tunnel.waitForReady();
}
......@@ -27,6 +29,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public readonly tunnelRemotePort: number;
public tunnelLocalPort!: number;
public localAddress?: URI;
private readonly _options: IConnectionOptions;
private readonly _server: net.Server;
......@@ -35,7 +38,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
private readonly _listeningListener: () => void;
private readonly _connectionListener: (socket: net.Socket) => void;
constructor(options: IConnectionOptions, tunnelRemotePort: number) {
constructor(options: IConnectionOptions, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
super();
this._options = options;
this._server = net.createServer();
......@@ -61,12 +64,14 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public async waitForReady(): Promise<this> {
// try to get the same port number as the remote port number...
const localPort = await findFreePort(this.tunnelRemotePort, 1, 1000);
const localPort = await findFreePort(this.suggestedLocalPort ?? this.tunnelRemotePort, 1, 1000);
// if that fails, the method above returns 0, which works out fine below...
this.tunnelLocalPort = (<net.AddressInfo>this._server.listen(localPort).address()).port;
const address = (<net.AddressInfo>this._server.listen(localPort).address());
this.tunnelLocalPort = address.port;
await this._barrier.wait();
this.localAddress = URI.from({ scheme: 'http', authority: 'localhost:' + address.port });
return this;
}
......@@ -96,6 +101,10 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
export class TunnelService implements ITunnelService {
_serviceBrand: undefined;
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<number> = new Emitter();
public onTunnelClosed: Event<number> = this._onTunnelClosed.event;
private readonly _tunnels = new Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>();
public constructor(
......@@ -116,33 +125,51 @@ export class TunnelService implements ITunnelService {
this._tunnels.clear();
}
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
openTunnel(remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort);
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort, localPort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
return resolvedTunnel.then(tunnel => ({
return resolvedTunnel.then(tunnel => {
const newTunnel = this.makeTunnel(tunnel);
this._onTunnelOpened.fire(newTunnel);
return newTunnel;
});
}
private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel {
return {
tunnelRemotePort: tunnel.tunnelRemotePort,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
dispose: () => {
const existing = this._tunnels.get(remotePort);
const existing = this._tunnels.get(tunnel.tunnelRemotePort);
if (existing) {
if (--existing.refcount <= 0) {
existing.value.then(tunnel => tunnel.dispose());
this._tunnels.delete(remotePort);
this._tunnels.delete(tunnel.tunnelRemotePort);
this._onTunnelClosed.fire(tunnel.tunnelRemotePort);
}
}
}
}));
};
}
async closeTunnel(remotePort: number): Promise<void> {
if (this._tunnels.has(remotePort)) {
const value = this._tunnels.get(remotePort)!;
(await value.value).dispose();
value.refcount = 0;
}
}
private retainOrCreateTunnel(remoteAuthority: string, remotePort: number): Promise<RemoteTunnel> | undefined {
private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
const existing = this._tunnels.get(remotePort);
if (existing) {
++existing.refcount;
......@@ -162,8 +189,9 @@ export class TunnelService implements ITunnelService {
logService: this.logService
};
const tunnel = createRemoteTunnel(options, remotePort);
this._tunnels.set(remotePort, { refcount: 1, value: tunnel });
const tunnel = createRemoteTunnel(options, remotePort, localPort);
// Using makeTunnel here for the value does result in dispose getting called twice, but it also ensures that _onTunnelClosed will be fired when closeTunnel is called.
this._tunnels.set(remotePort, { refcount: 1, value: tunnel.then(value => this.makeTunnel(value)) });
return tunnel;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册