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

Propagate and handle connection/disconnection events #1142

Merged
merged 14 commits into from
Mar 22, 2023
Merged
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
223 changes: 127 additions & 96 deletions package.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/api/Configuration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import * as vscode from 'vscode';

export type SourceDateMode = "edit"|"diff";

const getConfiguration = (): vscode.WorkspaceConfiguration => {
return vscode.workspace.getConfiguration(`code-for-ibmi`);
}
Expand Down Expand Up @@ -29,7 +31,7 @@ export namespace ConnectionConfiguration {
autoConvertIFSccsid: boolean;
hideCompileErrors: string[];
enableSourceDates: boolean;
sourceDateMode: "edit"|"diff";
sourceDateMode: SourceDateMode;
sourceDateGutter: boolean;
encodingFor5250: string;
terminalFor5250: string;
Expand Down
51 changes: 33 additions & 18 deletions src/api/IBMi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import * as vscode from "vscode";
import * as node_ssh from "node-ssh";
import { ConnectionConfiguration } from "./Configuration";

import {Tools} from './Tools';
import { Tools } from './Tools';
import path from 'path';
import { ConnectionData, CommandData, StandardIO, CommandResult } from "../typings";
import * as configVars from './configVars';
import { instance } from "../instantiate";
import IBMiContent from "./IBMiContent";

export interface MemberParts {
asp?: string
Expand Down Expand Up @@ -113,7 +115,7 @@ export default class IBMi {
if (!connectionObject.privateKey) (connectionObject.privateKey = null);

configVars.replaceAll(connectionObject);

return await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `Connecting`,
Expand Down Expand Up @@ -576,8 +578,8 @@ export default class IBMi {
// give user option to set bash as default shell.
try {
// make sure sql is enabled and bash is installed on system
if (this.config.enableSQL &&
this.remoteFeatures[`bash`]) {
if (this.config.enableSQL &&
this.remoteFeatures[`bash`]) {
const bashShellPath = '/QOpenSys/pkgs/bin/bash';
const commandShellResult = await this.sendCommand({
command: `echo $SHELL`
Expand All @@ -586,7 +588,7 @@ export default class IBMi {
let userDefaultShell = commandShellResult.stdout.trim();
if (userDefaultShell !== bashShellPath) {
vscode.window.showInformationMessage(`IBM recommends using bash as your default shell.`, `Set shell to bash?`, `Read More`,).then(async choice => {
switch (choice) {
switch (choice) {
case `Set shell to bash?`:
statement = `CALL QSYS2.SET_PASE_SHELL_INFO('*CURRENT', '/QOpenSys/pkgs/bin/bash')`;
output = await this.sendCommand({
Expand Down Expand Up @@ -646,7 +648,10 @@ export default class IBMi {
vscode.window.showWarningMessage(`Code for IBM i may not function correctly until your user has a home directory. Please set a home directory using CHGUSRPRF USRPRF(${connectionObject.username.toUpperCase()}) HOMEDIR('/home/${connectionObject.username.toLowerCase()}')`);
}

instance.setConnection(this);
vscode.workspace.getConfiguration().update(`workbench.editor.enablePreview`, false, true);
await vscode.commands.executeCommand(`setContext`, `code-for-ibmi:connected`, true);
instance.fire("connected");

return {
success: true
Expand All @@ -664,7 +669,7 @@ export default class IBMi {
error: e
};
}
finally{
finally {
ConnectionConfiguration.update(this.config!);
}
}
Expand Down Expand Up @@ -711,9 +716,8 @@ export default class IBMi {
async sendCommand(options: CommandData): Promise<CommandResult> {
let commands: string[] = [];
if (options.env) {
commands.push(...Object.entries(options.env).map(([key, value]) => `export ${key}="${
value?.replace(/\$/g, `\\$`).replace(/"/g, `\\"`) || ``
}"`))
commands.push(...Object.entries(options.env).map(([key, value]) => `export ${key}="${value?.replace(/\$/g, `\\$`).replace(/"/g, `\\"`) || ``
}"`))
}

commands.push(options.command);
Expand Down Expand Up @@ -774,14 +778,25 @@ export default class IBMi {
this.commandsExecuted += 1;
}

end() {
async end() {
this.client.connection.removeAllListeners();
this.client.dispose();

if (this.outputChannel) {
this.outputChannel.hide();
this.outputChannel.dispose();
}

await Promise.all([
vscode.commands.executeCommand("code-for-ibmi.refreshObjectBrowser"),
vscode.commands.executeCommand("code-for-ibmi.refreshLibraryListView"),
vscode.commands.executeCommand("code-for-ibmi.refreshIFSBrowser")
]);

instance.setConnection(undefined);
instance.fire(`disconnected`);
await vscode.commands.executeCommand(`setContext`, `code-for-ibmi:connected`, false);
vscode.window.showInformationMessage(`Disconnected from ${this.currentHost}.`);
}

/**
Expand Down Expand Up @@ -887,27 +902,27 @@ export default class IBMi {

return result;
}
async uploadFiles(files: {local : string | vscode.Uri, remote : string}[], options?: node_ssh.SSHPutFilesOptions){
await this.client.putFiles(files.map(f => {return {local: this.fileToPath(f.local), remote: f.remote}}), options);
async uploadFiles(files: { local: string | vscode.Uri, remote: string }[], options?: node_ssh.SSHPutFilesOptions) {
await this.client.putFiles(files.map(f => { return { local: this.fileToPath(f.local), remote: f.remote } }), options);
}

async downloadFile(localFile: string | vscode.Uri, remoteFile: string){
async downloadFile(localFile: string | vscode.Uri, remoteFile: string) {
await this.client.getFile(this.fileToPath(localFile), remoteFile);
}

async uploadDirectory(localDirectory: string | vscode.Uri, remoteDirectory : string, options?: node_ssh.SSHGetPutDirectoryOptions){
async uploadDirectory(localDirectory: string | vscode.Uri, remoteDirectory: string, options?: node_ssh.SSHGetPutDirectoryOptions) {
await this.client.putDirectory(this.fileToPath(localDirectory), remoteDirectory, options);
}

async downloadDirectory(localDirectory: string | vscode.Uri, remoteDirectory: string, options?: node_ssh.SSHGetPutDirectoryOptions){
async downloadDirectory(localDirectory: string | vscode.Uri, remoteDirectory: string, options?: node_ssh.SSHGetPutDirectoryOptions) {
await this.client.getDirectory(this.fileToPath(localDirectory), remoteDirectory, options);
}

fileToPath(file : string | vscode.Uri) : string{
if(typeof file === "string"){
fileToPath(file: string | vscode.Uri): string {
if (typeof file === "string") {
return file;
}
else{
else {
return file.fsPath;
}
}
Expand Down
85 changes: 56 additions & 29 deletions src/api/Instance.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,66 @@
import * as vscode from "vscode";
import IBMi from "./IBMi";
import IBMiContent from "./IBMiContent";
import {ConnectionStorage} from "./Storage";
import { ConnectionStorage, GlobalStorage } from "./Storage";
import { ConnectionConfiguration } from "./Configuration";
import { IBMiEvent } from "../typings";

export default class Instance {
connection: IBMi|undefined;
content: IBMiContent|undefined;
storage: ConnectionStorage|undefined;
emitter: vscode.EventEmitter<any>|undefined;
events: {event: string, func: Function}[];

constructor() {
this.events = [];
}

getConnection() {
return this.connection;
}
private connection: IBMi | undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is welcome change. Fingers crossed anyone using our extension API isn't using these!

private content: IBMiContent | undefined;
private storage: ConnectionStorage;
private emitter: vscode.EventEmitter<IBMiEvent> = new vscode.EventEmitter();
private events: { event: IBMiEvent, func: Function }[] = [];

async setConfig(newConfig: ConnectionConfiguration.Parameters) {
await ConnectionConfiguration.update(newConfig);
if (this.connection) this.connection.config = newConfig;
}
getConfig() {
return this.connection?.config;
}
getContent () {
return this.content;
constructor(context: vscode.ExtensionContext) {
this.events = [];
this.storage = new ConnectionStorage(context);
this.emitter.event(e => {
this.events.filter(event => event.event === e)
.forEach(event => event.func());
})
}

async setConnection(connection?: IBMi) {
if (connection) {
this.connection = connection;
this.storage.setConnectionName(connection.currentConnectionName);
this.content = new IBMiContent(connection);
await GlobalStorage.get().setLastConnection(connection.currentConnectionName);
}
getStorage () {
return this.storage;
else {
this.connection = undefined;
this.content = undefined;
this.storage.setConnectionName("");
}
}

onEvent(event: "connected" | "disconnected" | "deployLocation", func: Function): void {
this.events.push({event, func});
}
};
getConnection() {
return this.connection;
}

async setConfig(newConfig: ConnectionConfiguration.Parameters) {
await ConnectionConfiguration.update(newConfig);
if (this.connection) this.connection.config = newConfig;
}

getConfig() {
return this.connection?.config;
}

getContent() {
return this.content;
}

getStorage() {
return this.storage.ready ? this.storage : undefined;
}

onEvent(event: IBMiEvent, func: Function): void {
this.events.push({ event, func });
}

fire(event: IBMiEvent) {
this.emitter?.fire(event);
}
}
16 changes: 15 additions & 1 deletion src/api/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,24 @@ export class GlobalStorage extends Storage {
}

export class ConnectionStorage extends Storage {
constructor(context: vscode.ExtensionContext, readonly connectionName: string) {
private connectionName: string = "";
constructor(context: vscode.ExtensionContext) {
super(context);
}

get ready(): boolean {
if (this.connectionName) {
return true;
}
else {
return false;
}
}

setConnectionName(connectionName: string) {
this.connectionName = connectionName;
}

protected getStorageKey(key: string): string {
return `${this.connectionName}.${key}`;
}
Expand Down
32 changes: 16 additions & 16 deletions src/api/debug/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import path from "path";
import * as certificates from "./certificates";
import * as server from "./server";
import { copyFileSync } from "fs";
import { instance } from "../../instantiate";

const debugExtensionId = `IBM.ibmidebug`;

Expand All @@ -17,7 +18,7 @@ const localCertContext = `code-for-ibmi:debug.local`;
let connectionConfirmed = false;
let temporaryPassword: string | undefined;

export async function initialise(instance: Instance, context: ExtensionContext) {
export async function initialize(context: ExtensionContext) {
const debugExtensionAvailable = () => {
const debugclient = vscode.extensions.getExtension(debugExtensionId);
return debugclient !== undefined;
Expand Down Expand Up @@ -102,9 +103,9 @@ export async function initialise(instance: Instance, context: ExtensionContext)

vscode.debug.onDidTerminateDebugSession(async session => {
if (session.configuration.type === `IBMiDebug`) {
const connection = instance.connection;
const connection = instance.getConnection();

server.getStuckJobs(connection?.currentUser!, instance.content!).then(jobIds => {
server.getStuckJobs(connection?.currentUser!, instance.getContent()!).then(jobIds => {
if (jobIds.length > 0) {
vscode.window.showInformationMessage(`You have ${jobIds.length} debug job${jobIds.length !== 1 ? `s` : ``} stuck at MSGW under your user profile.`, `End jobs`, `Ignore`)
.then(selection => {
Expand All @@ -119,7 +120,7 @@ export async function initialise(instance: Instance, context: ExtensionContext)

vscode.commands.registerCommand(`code-for-ibmi.debug.activeEditor`, async () => {
if (debugExtensionAvailable()) {
const connection = instance.connection;
const connection = instance.getConnection();
if (connection) {
if (connection.remoteFeatures[`startDebugService.sh`]) {
const activeEditor = vscode.window.activeTextEditor;
Expand Down Expand Up @@ -159,7 +160,7 @@ export async function initialise(instance: Instance, context: ExtensionContext)
}),

vscode.commands.registerCommand(`code-for-ibmi.debug.setup.remote`, async () => {
const connection = instance.connection;
const connection = instance.getConnection();
if (connection) {
const ptfInstalled = await debugPTFInstalled();

Expand Down Expand Up @@ -207,7 +208,7 @@ export async function initialise(instance: Instance, context: ExtensionContext)
}),

vscode.commands.registerCommand(`code-for-ibmi.debug.setup.local`, async () => {
const connection = instance.connection;
const connection = instance.getConnection();

if (connection) {
const ptfInstalled = await debugPTFInstalled();
Expand Down Expand Up @@ -259,7 +260,7 @@ export async function initialise(instance: Instance, context: ExtensionContext)
}),

vscode.commands.registerCommand(`code-for-ibmi.debug.start`, async () => {
const connection = instance.connection;
const connection = instance.getConnection();
if (connection) {
const ptfInstalled = await debugPTFInstalled();
if (ptfInstalled) {
Expand All @@ -272,7 +273,7 @@ export async function initialise(instance: Instance, context: ExtensionContext)


progress.report({ increment: 33, message: `Checking if service is already running.` });
const existingDebugService = await server.getRunningJob(connection.config?.debugPort || "8005", instance.content!);
const existingDebugService = await server.getRunningJob(connection.config?.debugPort || "8005", instance.getContent()!);

if (existingDebugService) {
const confirmEndServer = await vscode.window.showInformationMessage(`Starting debug service`, {
Expand Down Expand Up @@ -316,18 +317,18 @@ export async function initialise(instance: Instance, context: ExtensionContext)
);

// Run during startup:

if (instance.connection) {
if (instance.connection.remoteFeatures[`startDebugService.sh`]) {
instance.onEvent("connected", async () => {
const connection = instance.getConnection();
if (connection && connection?.remoteFeatures[`startDebugService.sh`]) {
vscode.commands.executeCommand(`setContext`, ptfContext, true);

const remoteCerts = await certificates.checkRemoteExists(instance.connection);
const remoteCerts = await certificates.checkRemoteExists(connection);

if (remoteCerts) {
vscode.commands.executeCommand(`setContext`, remoteCertContext, true);

if (instance.connection.config!.debugIsSecure) {
const localExists = await certificates.checkLocalExists(instance.connection);
if (connection.config!.debugIsSecure) {
const localExists = await certificates.checkLocalExists(connection);

if (localExists) {
vscode.commands.executeCommand(`setContext`, localCertContext, true);
Expand All @@ -342,8 +343,7 @@ export async function initialise(instance: Instance, context: ExtensionContext)
}
}
}
}

});
}

interface DebugOptions {
Expand Down
Loading