Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple connections #2505

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -888,32 +888,37 @@
}
},
"commands": [
{
"command": "code-for-ibmi.switchActiveConnection",
"title": "Switch Active Connection",
"category": "IBM i"
},
{
"command": "code-for-ibmi.setToDefault",
"title": "Reset to Default Profile",
"category": "IBM i",
"enablement": "code-for-ibmi:connected == true",
"enablement": "code-for-ibmi:connected",
"icon": "$(arrow-circle-right)"
},
{
"command": "code-for-ibmi.manageCommandProfile",
"title": "Create/edit command profile...",
"category": "IBM i",
"enablement": "code-for-ibmi:connected == true",
"enablement": "code-for-ibmi:connected",
"icon": "$(library)"
},
{
"command": "code-for-ibmi.deleteCommandProfile",
"title": "Delete command profile...",
"category": "IBM i",
"enablement": "code-for-ibmi:connected == true && code-for-ibmi:hasProfiles == true",
"enablement": "code-for-ibmi:connected && code-for-ibmi:hasProfiles == true",
"icon": "$(remove)"
},
{
"command": "code-for-ibmi.loadCommandProfile",
"title": "Load command profile",
"category": "IBM i",
"enablement": "code-for-ibmi:connected == true",
"enablement": "code-for-ibmi:connected",
"icon": "$(arrow-circle-right)"
},
{
Expand Down Expand Up @@ -1772,7 +1777,7 @@
{
"id": "connectionBrowser",
"name": "Servers",
"when": "!code-for-ibmi:connecting && !code-for-ibmi:connected && !code-for-ibmi:connectionBrowserDisabled"
"when": "!code-for-ibmi:connectionBrowserDisabled"
},
{
"id": "profilesView",
Expand Down
135 changes: 93 additions & 42 deletions src/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { VsCodeConfig } from "./config/Configuration";
import { EventEmitter } from "stream";
import { ConnectionStorage } from "./api/configuration/storage/ConnectionStorage";
import { VscodeTools } from "./ui/Tools";
import { refreshDebugSensitiveItems } from "./debug/server";

type IBMiEventSubscription = {
func: Function,
transient?: boolean
};

type SubscriptionMap = Map<string, IBMiEventSubscription>
type IBMiEventData = {event: IBMiEvent, connection?: IBMi};

export interface ConnectionOptions {
data: ConnectionData,
Expand All @@ -24,7 +26,12 @@ export interface ConnectionOptions {
}

export default class Instance {
private connection: IBMi | undefined;
private connections: IBMi[] = [];

private activeConnection = -1;
private get connection(): IBMi|undefined {
return this.connections[this.activeConnection];
}

private output = {
channel: vscode.window.createOutputChannel(`Code for IBM i`),
Expand All @@ -33,7 +40,7 @@ export default class Instance {
};

private storage: ConnectionStorage;
private emitter: vscode.EventEmitter<IBMiEvent> = new vscode.EventEmitter();
private emitter: vscode.EventEmitter<IBMiEventData> = new vscode.EventEmitter();
private subscribers: Map<IBMiEvent, SubscriptionMap> = new Map;

private deprecationCount = 0; //TODO: remove in v3.0.0
Expand Down Expand Up @@ -61,6 +68,17 @@ export default class Instance {
this.output.writeCount = 0;
}

public refreshUi() {
Promise.all([
vscode.commands.executeCommand("code-for-ibmi.refreshObjectBrowser"),
vscode.commands.executeCommand("code-for-ibmi.refreshLibraryListView"),
vscode.commands.executeCommand("code-for-ibmi.refreshIFSBrowser"),
vscode.commands.executeCommand("code-for-ibmi.refreshConnections"),
vscode.commands.executeCommand(`setContext`, `code-for-ibmi:connected`, this.getActiveConnections().length),
refreshDebugSensitiveItems()
]);
}

connect(options: ConnectionOptions): Promise<ConnectionResult> {
const connection = new IBMi();

Expand Down Expand Up @@ -94,7 +112,7 @@ export default class Instance {
});
}

this.disconnect();
this.disconnect(conn);

if (reconnect) {
await this.connect({...options, reconnecting: true});
Expand Down Expand Up @@ -133,11 +151,14 @@ export default class Instance {
});

if (result.success) {
await this.setConnection(connection);
await this.addConnection(connection);
this.validateActiveIndex();

this.refreshUi();
break;

} else {
await this.disconnect();
await this.disconnect(connection);
if (options.reconnecting && await vscode.window.showWarningMessage(`Could not reconnect`, {
modal: true,
detail: `Reconnection has failed. Would you like to try again?\n\n${customError || `No error provided.`}`
Expand All @@ -160,42 +181,72 @@ export default class Instance {
});
}

async disconnect() {
await this.setConnection();

await Promise.all([
vscode.commands.executeCommand("code-for-ibmi.refreshObjectBrowser"),
vscode.commands.executeCommand("code-for-ibmi.refreshLibraryListView"),
vscode.commands.executeCommand("code-for-ibmi.refreshIFSBrowser")
]);
async disconnect(connection: IBMi|undefined = this.connection) {
if (connection) {
connection.dispose();
}

this.refreshUi();
}

private async setConnection(connection?: IBMi) {
if (this.connection) {
await this.connection.dispose();
}
getConnectionById(id: string) {
return this.connections.find(c => c.id === id);
}

if (connection) {
connection.setDisconnectedCallback(async () => {
this.setConnection();
this.fire(`disconnected`);
});

this.connection = connection;
this.storage.setConnectionName(connection.currentConnectionName);
await IBMi.GlobalStorage.setLastConnection(connection.currentConnectionName);
this.fire(`connected`);
}
else {
this.connection = undefined;
this.storage.setConnectionName("");
public setActiveConnection(name: string) {
const index = this.connections.findIndex(c => c.currentConnectionName === name);
if (index !== -1) {
const shouldRefresh = this.activeConnection !== index;
this.activeConnection = index;

if (shouldRefresh) {
this.refreshUi();
this.fire({event: `switched`, connection: this.connection});
}

vscode.window.showInformationMessage(`Switched to connection ${name}.`);
} else {
this.activeConnection = -1;
this.fire({event: `switched`, connection: undefined});
}
}

private validateActiveIndex() {
this.activeConnection = this.connections.length - 1;
}

private async addConnection(connection: IBMi) {
connection.setDisconnectedCallback(async (conn) => {
this.fire({event: `disconnected`, connection: conn});

const existingConnection = this.connections.findIndex(c => c.currentConnectionName === conn.currentConnectionName);
if (existingConnection !== -1) {
this.connections.splice(existingConnection, 1);
this.validateActiveIndex();
}
});

this.connections.push(connection);
this.validateActiveIndex();

this.storage.setConnectionName(connection.currentConnectionName);
await IBMi.GlobalStorage.setLastConnection(connection.currentConnectionName);
this.fire({event: `connected`, connection});
}

/**
* @deprecated Will be removed in `v3.0.0`; use {@link IBMi.getActiveConnection()} instead
*/
getConnection(): IBMi|undefined {
return this.connections[0];
}

await vscode.commands.executeCommand(`setContext`, `code-for-ibmi:connected`, connection !== undefined);
getActiveConnection() {
return this.connection!;
}

getConnection() {
return this.connection;
getActiveConnections() {
return this.connections;
}

async setConfig(newConfig: ConnectionConfig) {
Expand Down Expand Up @@ -253,28 +304,28 @@ export default class Instance {
console.warn("[Code for IBM i] Deprecation warning: you are using Instance::onEvent which is deprecated and will be removed in v3.0.0. Please use Instance::subscribe instead.");
}

fire(event: IBMiEvent) {
this.emitter?.fire(event);
fire(data: IBMiEventData) {
this.emitter?.fire(data);
}

async processEvent(event: IBMiEvent) {
const eventSubscribers = this.getSubscribers(event)
console.time(event);
async processEvent(data: IBMiEventData) {
const eventSubscribers = this.getSubscribers(data.event)
console.time(data.event);
for (const [identity, callable] of eventSubscribers.entries()) {
try {
console.time(identity);
await callable.func();
await callable.func(data.connection);
console.timeEnd(identity);
}
catch (error) {
console.error(`${event} event function ${identity} failed`, error);
console.error(`${data.event} event function ${identity} failed`, error);
}
finally {
if (callable.transient) {
eventSubscribers.delete(identity);
}
}
}
console.timeEnd(event);
console.timeEnd(data.event);
}
}
6 changes: 5 additions & 1 deletion src/api/IBMi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ConnectionConfig } from './configuration/config/types';
import { EditorPath } from '../typings';

export interface MemberParts extends IBMiMember {
basename: string
basename: string,
}

export type ConnectionMessageType = 'info' | 'warning' | 'error';
Expand Down Expand Up @@ -120,6 +120,10 @@ export default class IBMi {
//Maximum admited length for command's argument - any command whose arguments are longer than this won't be executed by the shell
maximumArgsLength = 0;

public get id() {
return this.currentConnectionName;
}

public appendOutput: (text: string) => void = (text) => {
process.stdout.write(text);
};
Expand Down
4 changes: 3 additions & 1 deletion src/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import IBMi from "./IBMi";

export type DeploymentMethod = "all" | "staged" | "unstaged" | "changed" | "compare";

Expand Down Expand Up @@ -131,9 +132,10 @@ export interface FileError {

export interface QsysFsOptions {
readonly?: boolean
connection?: IBMi
}

export type IBMiEvent = "connected" | "disconnected" | "deployLocation" | "deploy"
export type IBMiEvent = "connected" | "disconnected" | "deployLocation" | "deploy" | "switched"

export interface WithPath {
path: string
Expand Down
4 changes: 2 additions & 2 deletions src/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { BrowserItem } from "../ui/types";
export function registerActionsCommands(instance: Instance): Disposable[] {
return [
commands.registerCommand(`code-for-ibmi.runAction`, async (target: TreeItem | BrowserItem | Uri, group?: any, action?: Action, method?: DeploymentMethod, workspaceFolder?: WorkspaceFolder) => {
const connection = instance.getConnection()!;
const connection = instance.getActiveConnection()!;
const editor = window.activeTextEditor;
let uri;
let browserItem;
Expand Down Expand Up @@ -93,7 +93,7 @@ export function registerActionsCommands(instance: Instance): Disposable[] {

let initialPath = ``;
const editor = window.activeTextEditor;
const connection = instance.getConnection();
const connection = instance.getActiveConnection();

if (editor && connection) {
const config = connection.getConfig();
Expand Down
26 changes: 24 additions & 2 deletions src/commands/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function registerConnectionCommands(context: ExtensionContext, instance:
return [
commands.registerCommand(`code-for-ibmi.connectDirect`,
async (connectionData: ConnectionData, reloadSettings = false, savePassword = false): Promise<boolean> => {
const existingConnection = instance.getConnection();
const existingConnection = instance.getActiveConnection();

if (existingConnection) {
return false;
Expand All @@ -23,11 +23,33 @@ export function registerConnectionCommands(context: ExtensionContext, instance:
}
),
commands.registerCommand(`code-for-ibmi.disconnect`, async (silent?: boolean) => {
if (instance.getConnection()) {
if (instance.getActiveConnection()) {
await safeDisconnect();
} else if (!silent) {
window.showErrorMessage(`Not currently connected to any system.`);
}
}),

commands.registerCommand(`code-for-ibmi.switchActiveConnection`, () => {
const availableConnections = instance.getActiveConnections();

if (availableConnections.length === 0) {
window.showErrorMessage(`No connections found.`);
return;
}

if (availableConnections.length === 1) {
window.showInformationMessage(`Only one connection found. Automatically connecting.`);
return;
}

const connectionNames = availableConnections.map(c => c.currentConnectionName);

window.showQuickPick(connectionNames, {placeHolder: `Select a connection to switch to`}).then(async (selectedConnection) => {
if (selectedConnection) {
instance.setActiveConnection(selectedConnection);
}
});
})
]
}
4 changes: 2 additions & 2 deletions src/commands/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function registerOpenCommands(instance: Instance): Disposable[] {

return [
commands.registerCommand(`code-for-ibmi.openEditable`, async (path: string, options?: OpenEditableOptions) => {
const connection = instance.getConnection()!;
const connection = instance.getActiveConnection()!;
console.log(path);
options = options || {};
options.readonly = options.readonly || connection.getContent().isProtectedPath(path);
Expand Down Expand Up @@ -119,7 +119,7 @@ export function registerOpenCommands(instance: Instance): Disposable[] {
};

const LOADING_LABEL = `Please wait`;
const connection = instance.getConnection();
const connection = instance.getActiveConnection();
if (!connection) return;

const storage = instance.getStorage();
Expand Down
Loading
Loading