From c4de790602133dda1e9cf2633bc63071a9ed218a Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Wed, 28 Aug 2024 10:49:54 -0400
Subject: [PATCH 01/28] Extend the base API to allow other extensions to add
their own components.
Signed-off-by: worksofliam
---
src/api/IBMi.ts | 4 +--
src/components/component.ts | 50 +++----------------------------------
src/components/manager.ts | 49 ++++++++++++++++++++++++++++++++++++
src/extension.ts | 9 ++++++-
src/typings.ts | 4 ++-
5 files changed, 66 insertions(+), 50 deletions(-)
create mode 100644 src/components/manager.ts
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index b53ba312f..4317e81c9 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -6,7 +6,6 @@ import { parse } from 'csv-parse/sync';
import { existsSync } from "fs";
import os from "os";
import path from 'path';
-import { ComponentId, ComponentManager } from "../components/component";
import { CopyToImport } from "../components/copyToImport";
import { instance } from "../instantiate";
import { CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand, SpecialAuthorities, WrapResult } from "../typings";
@@ -17,6 +16,7 @@ import { Tools } from './Tools';
import * as configVars from './configVars';
import { DebugConfiguration } from "./debug/config";
import { debugPTFInstalled } from "./debug/server";
+import { ComponentManager } from "../components/manager";
export interface MemberParts extends IBMiMember {
basename: string
@@ -1341,7 +1341,7 @@ export default class IBMi {
}
}
- getComponent(id: ComponentId) {
+ getComponent(id: string) {
return this.components.get(id);
}
diff --git a/src/components/component.ts b/src/components/component.ts
index e40002c62..4f5518d17 100644
--- a/src/components/component.ts
+++ b/src/components/component.ts
@@ -1,7 +1,4 @@
import IBMi from "../api/IBMi";
-import { CopyToImport } from "./copyToImport";
-import { GetMemberInfo } from "./getMemberInfo";
-import { GetNewLibl } from "./getNewLibl";
export enum ComponentState {
NotChecked = `NotChecked`,
@@ -9,53 +6,14 @@ export enum ComponentState {
Installed = `Installed`,
Error = `Error`,
}
-interface ComponentRegistry {
- GetNewLibl?: GetNewLibl;
- CopyToImport?: CopyToImport;
- GetMemberInfo?: GetMemberInfo;
-}
-
-export type ComponentId = keyof ComponentRegistry;
-export abstract class ComponentT {
+export class ComponentT {
public state: ComponentState = ComponentState.NotChecked;
public currentVersion: number = 0;
constructor(public connection: IBMi) { }
- abstract getInstalledVersion(): Promise;
- abstract checkState(): Promise
- abstract getState(): ComponentState;
-}
-
-export class ComponentManager {
- private registered: ComponentRegistry = {};
-
- public async startup(connection: IBMi) {
- this.registered.GetNewLibl = new GetNewLibl(connection);
- await ComponentManager.checkState(this.registered.GetNewLibl);
-
- this.registered.CopyToImport = new CopyToImport(connection);
- await ComponentManager.checkState(this.registered.CopyToImport);
-
- this.registered.GetMemberInfo = new GetMemberInfo(connection);
- await ComponentManager.checkState(this.registered.GetMemberInfo);
- }
-
- // TODO: return type based on ComponentIds
- get(id: ComponentId): T | undefined {
- const component = this.registered[id];
- if (component && component.getState() === ComponentState.Installed) {
- return component as T;
- }
- }
-
- private static async checkState(component: ComponentT) {
- try {
- await component.checkState();
- } catch (e) {
- console.log(component);
- console.log(`Error checking state for ${component.constructor.name}`, e);
- }
- }
+ async getInstalledVersion(): Promise {return};
+ async checkState(): Promise {return false}
+ getState(): ComponentState {return this.state};
}
\ No newline at end of file
diff --git a/src/components/manager.ts b/src/components/manager.ts
new file mode 100644
index 000000000..bf66cea0b
--- /dev/null
+++ b/src/components/manager.ts
@@ -0,0 +1,49 @@
+import IBMi from "../api/IBMi";
+import { ComponentState, ComponentT } from "./component";
+import { CopyToImport } from "./copyToImport";
+import { GetMemberInfo } from "./getMemberInfo";
+import { GetNewLibl } from "./getNewLibl";
+
+export class ComponentRegistry {
+ private allComponents: (typeof ComponentT)[] = [GetNewLibl, CopyToImport, GetMemberInfo];
+
+ public registerComponent(component: typeof ComponentT) {
+ this.allComponents.push(component);
+ }
+
+ public getComponents() {
+ return this.allComponents;
+ }
+}
+
+export const ExtensionComponentRegistry = new ComponentRegistry();
+
+interface ComponentList {[name: string]: ComponentT};
+
+export class ComponentManager {
+ private registered: ComponentList = {};
+
+ public async startup(connection: IBMi) {
+ for (const Component of ExtensionComponentRegistry.getComponents()) {
+ const instance = new Component(connection);
+ this.registered[Component.name] = instance;
+ await ComponentManager.checkState(instance);
+ }
+ }
+
+ get(id: string): T | undefined {
+ const component = this.registered[id];
+ if (component && component.getState() === ComponentState.Installed) {
+ return component as T;
+ }
+ }
+
+ private static async checkState(component: ComponentT) {
+ try {
+ await component.checkState();
+ } catch (e) {
+ console.log(component);
+ console.log(`Error checking state for ${component.constructor.name}`, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index a2b4c97bc..67f89f50f 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -31,6 +31,7 @@ import { initializeIFSBrowser } from "./views/ifsBrowser";
import { initializeObjectBrowser } from "./views/objectBrowser";
import { initializeSearchView } from "./views/searchView";
import { SettingsUI } from "./webviews/settings";
+import { ExtensionComponentRegistry } from "./components/manager";
export async function activate(context: ExtensionContext): Promise {
// Use the console to output diagnostic information (console.log) and errors (console.error)
@@ -125,7 +126,13 @@ export async function activate(context: ExtensionContext): Promise
commands.executeCommand("code-for-ibmi.refreshProfileView");
});
- return { instance, customUI: () => new CustomUI(), deployTools: DeployTools, evfeventParser: parseErrors, tools: Tools };
+ return {
+ instance, customUI: () => new CustomUI(),
+ deployTools: DeployTools,
+ evfeventParser: parseErrors,
+ tools: Tools,
+ componentRegistry: ExtensionComponentRegistry
+ };
}
async function fixLoginSettings() {
diff --git a/src/typings.ts b/src/typings.ts
index cceef76f0..381b4e6cb 100644
--- a/src/typings.ts
+++ b/src/typings.ts
@@ -5,13 +5,15 @@ import { CustomUI } from "./api/CustomUI";
import Instance from "./api/Instance";
import { Tools } from "./api/Tools";
import { DeployTools } from "./api/local/deployTools";
+import { ComponentRegistry } from './components/manager';
export interface CodeForIBMi {
instance: Instance,
customUI: () => CustomUI,
deployTools: typeof DeployTools,
evfeventParser: (lines: string[]) => Map,
- tools: typeof Tools
+ tools: typeof Tools,
+ componentRegistry: ComponentRegistry
}
export type DeploymentMethod = "all" | "staged" | "unstaged" | "changed" | "compare";
From 3d4a6115564a55166b7b211a596bec6ed62a87f7 Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Mon, 2 Sep 2024 21:33:16 -0400
Subject: [PATCH 02/28] Implement custom casting function
Signed-off-by: worksofliam
---
src/api/IBMi.ts | 3 +--
src/api/Tools.ts | 7 +++++++
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index fe2efe28b..fca706830 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -1416,10 +1416,9 @@ export default class IBMi {
return parse(csvContent, {
columns: true,
skip_empty_lines: true,
- cast: true,
onRecord(record) {
for (const key of Object.keys(record)) {
- record[key] = record[key] === ` ` ? `` : record[key];
+ record[key] = record[key] === ` ` ? `` : Tools.assumeType(record[key]);
}
return record;
}
diff --git a/src/api/Tools.ts b/src/api/Tools.ts
index 38433c105..27b4d0ef0 100644
--- a/src/api/Tools.ts
+++ b/src/api/Tools.ts
@@ -390,6 +390,13 @@ export namespace Tools {
}
}
+ export function assumeType(str: string) {
+ // The number is already generated on the server.
+ // So, we assume that if the string starts with a 0, it is a string.
+ if (str[0] === `0` || str.length > 10) return str;
+ return Number(str) || str;
+ }
+
const activeContexts: Map = new Map;
/**
* Runs a function while a context value is set to true.
From 00482be4e9d85dc20a480650ad954711af45f2d6 Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Mon, 2 Sep 2024 21:33:36 -0400
Subject: [PATCH 03/28] Remove use of deprecated function
Signed-off-by: worksofliam
---
src/filesystems/qsys/extendedContent.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/filesystems/qsys/extendedContent.ts b/src/filesystems/qsys/extendedContent.ts
index 477bd3154..85928f2a3 100644
--- a/src/filesystems/qsys/extendedContent.ts
+++ b/src/filesystems/qsys/extendedContent.ts
@@ -52,11 +52,11 @@ export class ExtendedIBMiContent {
let rows;
if (sourceColourSupport)
- rows = await content.runSQL(
+ rows = await connection.runSQL(
`select srcdat, rtrim(translate(srcdta, ${SEU_GREEN_UL_RI_temp}, ${SEU_GREEN_UL_RI})) as srcdta from ${aliasPath}`
);
else
- rows = await content.runSQL(
+ rows = await connection.runSQL(
`select srcdat, srcdta from ${aliasPath}`
);
From 178c3912c703265301a30a2a50e5db312797973f Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Mon, 2 Sep 2024 21:34:31 -0400
Subject: [PATCH 04/28] Implement test for ensuring correct lines are returned
Signed-off-by: worksofliam
---
src/testing/content.ts | 46 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/src/testing/content.ts b/src/testing/content.ts
index a99b78ae3..9074f43ce 100644
--- a/src/testing/content.ts
+++ b/src/testing/content.ts
@@ -278,6 +278,52 @@ export const ContentSuite: TestSuite = {
},
},
+ {name: `Ensure source lines are correct`, test: async () => {
+ const connection = instance.getConnection();
+ const config = instance.getConfig()!;
+
+ assert.ok(config.enableSourceDates, `Source dates must be enabled for this test.`);
+
+ const tempLib = config!.tempLibrary;
+ const file = `LINES`;
+ const member = `THEMEMBER`;
+
+ await connection!.runCommand({ command: `CRTSRCPF FILE(${tempLib}/${file}) RCDLEN(112)`, noLibList: true });
+ await connection!.runCommand({ command: `ADDPFM FILE(${tempLib}/${file}) MBR(${member}) SRCTYPE(TXT)`, noLibList: true });
+
+ const aliasName = `${tempLib}.test_${file}_${member}`;
+ await connection?.runSQL(`CREATE OR REPLACE ALIAS ${aliasName} for "${tempLib}"."${file}"("${member}")`);
+
+ try {
+ await connection?.runSQL(`delete from ${aliasName}`);
+ } catch (e) {}
+
+ const inLines = [
+ `Hello world`,
+ `1`,
+ `001`,
+ `0002`,
+ `00003`,
+ ]
+
+ const lines = [
+ `insert into ${aliasName} (srcseq, srcdat, srcdta)`,
+ `values `,
+ inLines.map((line, index) => `(${index + 1}.00, 0, '${line}')`).join(`, `),
+ ];
+
+ await connection?.runSQL(lines.join(` `));
+
+ const theBadOneUri = getMemberUri({ library: tempLib, file, name: member, extension: `TXT` });
+
+ const memberContentBuf = await workspace.fs.readFile(theBadOneUri);
+ const fileContent = new TextDecoder().decode(memberContentBuf);
+
+ const outLines = fileContent.split(`\n`);
+
+ assert.deepStrictEqual(inLines, outLines);
+ }},
+
{
name: `Test runSQL (basic select)`, test: async () => {
const content = instance.getContent();
From 51e55df71ef1b2900d1b62156398c26f6d6787c3 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Wed, 18 Sep 2024 17:47:53 +0200
Subject: [PATCH 05/28] Code cleanup
Signed-off-by: Seb Julliand
---
src/components/manager.ts | 14 ++++++--------
src/extension.ts | 4 ++--
2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/src/components/manager.ts b/src/components/manager.ts
index bf66cea0b..a34ccf12a 100644
--- a/src/components/manager.ts
+++ b/src/components/manager.ts
@@ -5,7 +5,7 @@ import { GetMemberInfo } from "./getMemberInfo";
import { GetNewLibl } from "./getNewLibl";
export class ComponentRegistry {
- private allComponents: (typeof ComponentT)[] = [GetNewLibl, CopyToImport, GetMemberInfo];
+ private readonly allComponents: (typeof ComponentT)[] = [GetNewLibl, CopyToImport, GetMemberInfo];
public registerComponent(component: typeof ComponentT) {
this.allComponents.push(component);
@@ -16,23 +16,21 @@ export class ComponentRegistry {
}
}
-export const ExtensionComponentRegistry = new ComponentRegistry();
-
-interface ComponentList {[name: string]: ComponentT};
+export const extensionComponentRegistry = new ComponentRegistry();
export class ComponentManager {
- private registered: ComponentList = {};
+ private readonly registered: Map = new Map;
public async startup(connection: IBMi) {
- for (const Component of ExtensionComponentRegistry.getComponents()) {
+ for (const Component of extensionComponentRegistry.getComponents()) {
const instance = new Component(connection);
- this.registered[Component.name] = instance;
+ this.registered.set(Component.name, instance);
await ComponentManager.checkState(instance);
}
}
get(id: string): T | undefined {
- const component = this.registered[id];
+ const component = this.registered.get(id);
if (component && component.getState() === ComponentState.Installed) {
return component as T;
}
diff --git a/src/extension.ts b/src/extension.ts
index 67f89f50f..0f0965f4d 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -16,6 +16,7 @@ import * as Debug from './api/debug';
import { parseErrors } from "./api/errors/parser";
import { DeployTools } from "./api/local/deployTools";
import { Deployment } from "./api/local/deployment";
+import { extensionComponentRegistry } from "./components/manager";
import { IFSFS } from "./filesystems/ifsFs";
import { LocalActionCompletionItemProvider } from "./languages/actions/completion";
import { updateLocale } from "./locale";
@@ -31,7 +32,6 @@ import { initializeIFSBrowser } from "./views/ifsBrowser";
import { initializeObjectBrowser } from "./views/objectBrowser";
import { initializeSearchView } from "./views/searchView";
import { SettingsUI } from "./webviews/settings";
-import { ExtensionComponentRegistry } from "./components/manager";
export async function activate(context: ExtensionContext): Promise {
// Use the console to output diagnostic information (console.log) and errors (console.error)
@@ -131,7 +131,7 @@ export async function activate(context: ExtensionContext): Promise
deployTools: DeployTools,
evfeventParser: parseErrors,
tools: Tools,
- componentRegistry: ExtensionComponentRegistry
+ componentRegistry: extensionComponentRegistry
};
}
From d440c7286f495265f385afb7609a88b42d2870d3 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Wed, 18 Sep 2024 23:41:37 +0200
Subject: [PATCH 06/28] Components management refactoring
Signed-off-by: Seb Julliand
---
src/api/IBMi.ts | 13 ++--
src/components/component.ts | 77 +++++++++++++++++---
src/components/copyToImport.ts | 35 ++++-----
src/components/getMemberInfo.ts | 122 ++++++++++++--------------------
src/components/getNewLibl.ts | 84 +++++++++-------------
src/components/manager.ts | 31 ++++----
src/instantiate.ts | 14 ++--
src/testing/components.ts | 4 +-
src/views/ProfilesView.ts | 2 +-
9 files changed, 192 insertions(+), 190 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 4317e81c9..599bf0104 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -6,7 +6,9 @@ import { parse } from 'csv-parse/sync';
import { existsSync } from "fs";
import os from "os";
import path from 'path';
+import { IBMiComponent, IBMiComponentType } from "../components/component";
import { CopyToImport } from "../components/copyToImport";
+import { ComponentManager } from "../components/manager";
import { instance } from "../instantiate";
import { CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand, SpecialAuthorities, WrapResult } from "../typings";
import { CompileTools } from "./CompileTools";
@@ -16,7 +18,6 @@ import { Tools } from './Tools';
import * as configVars from './configVars';
import { DebugConfiguration } from "./debug/config";
import { debugPTFInstalled } from "./debug/server";
-import { ComponentManager } from "../components/manager";
export interface MemberParts extends IBMiMember {
basename: string
@@ -55,7 +56,7 @@ export default class IBMi {
/** User default CCSID is job default CCSID */
private userDefaultCCSID: number = 0;
- private components: ComponentManager = new ComponentManager();
+ private componentManager = new ComponentManager(this);
client: node_ssh.NodeSSH;
currentHost: string = ``;
@@ -927,7 +928,7 @@ export default class IBMi {
}
progress.report({ message: `Checking Code for IBM i components.` });
- await this.components.startup(this);
+ await this.componentManager.startup();
if (!reconnecting) {
vscode.workspace.getConfiguration().update(`workbench.editor.enablePreview`, false, true);
@@ -1341,8 +1342,8 @@ export default class IBMi {
}
}
- getComponent(id: string) {
- return this.components.get(id);
+ getComponent(type: IBMiComponentType): T | undefined {
+ return this.componentManager.get(type);
}
/**
@@ -1372,7 +1373,7 @@ export default class IBMi {
if (lastStmt) {
if ((asUpper?.startsWith(`SELECT`) || asUpper?.startsWith(`WITH`))) {
- const copyToImport = this.getComponent(`CopyToImport`);
+ const copyToImport = this.getComponent(CopyToImport);
if (copyToImport) {
returningAsCsv = copyToImport.wrap(lastStmt);
list.push(...returningAsCsv.newStatements);
diff --git a/src/components/component.ts b/src/components/component.ts
index 4f5518d17..f7ca0b92d 100644
--- a/src/components/component.ts
+++ b/src/components/component.ts
@@ -1,19 +1,80 @@
import IBMi from "../api/IBMi";
-export enum ComponentState {
+export const enum ComponentState {
NotChecked = `NotChecked`,
NotInstalled = `NotInstalled`,
Installed = `Installed`,
+ NeedUpdate = `NeedUpdate`,
Error = `Error`,
}
+export type IBMiComponentType = new (c: IBMi) => T;
-export class ComponentT {
- public state: ComponentState = ComponentState.NotChecked;
- public currentVersion: number = 0;
+/**
+ * Defines a component that is managed per IBM i.
+ *
+ * Any class extending {@link IBMiComponent} needs to register itself in the Component Registry.
+ *
+ * For example, this class:
+ * ```
+ * class MyIBMIComponent extends IBMiComponent {
+ * //implements getName(), getRemoteState() and update()
+ * }
+ * ```
+ * Must be registered like this:
+ * ```
+ * const codeForIBMiExtension = vscode.extensions.getExtension('halcyontechltd.code-for-ibmi');
+ * if (codeForIBMiExtension) {
+ * codeForIBMi = codeForIBMiExtension.isActive ? codeForIBMiExtension.exports : await codeForIBMiExtension.activate();
+ * codeForIBMi.componentRegistry.registerComponent(MyIBMIComponent);
+ * }
+ * ```
+ *
+ */
+export abstract class IBMiComponent {
+ private state = ComponentState.NotChecked;
- constructor(public connection: IBMi) { }
+ constructor(protected readonly connection: IBMi) {
- async getInstalledVersion(): Promise {return};
- async checkState(): Promise {return false}
- getState(): ComponentState {return this.state};
+ }
+
+ getState() {
+ return this.state;
+ }
+
+ async check() {
+ try {
+ this.state = await this.getRemoteState();
+ if (this.state !== ComponentState.Installed) {
+ this.state = await this.update();
+ }
+ }
+ catch (error) {
+ console.log(`Error occurred while checking component ${this.getName()}`);
+ console.log(error);
+ this.state = ComponentState.Error;
+ }
+
+ return this;
+ }
+
+ /**
+ * The name of this component; mainly used for display and logging purposes
+ *
+ * @returns a human-readable name
+ */
+ abstract getName(): string;
+
+ /**
+ * @returns the component's {@link ComponentState state} on the IBM i
+ */
+ protected abstract getRemoteState(): ComponentState | Promise;
+
+ /**
+ * Called whenever the components needs to be installed or updated, depending on its {@link ComponentState state}.
+ *
+ * The Component Manager is responsible for calling this, so the {@link ComponentState state} doesn't need to be checked here.
+ *
+ * @returns the component's {@link ComponentState state} after the update is done
+ */
+ protected abstract update(): ComponentState | Promise
}
\ No newline at end of file
diff --git a/src/components/copyToImport.ts b/src/components/copyToImport.ts
index ca1b57d4a..24b2d0458 100644
--- a/src/components/copyToImport.ts
+++ b/src/components/copyToImport.ts
@@ -1,27 +1,8 @@
-import IBMi from "../api/IBMi";
import { Tools } from "../api/Tools";
import { WrapResult } from "../typings";
-import { ComponentState, ComponentT } from "./component";
-
-export class CopyToImport implements ComponentT {
- private readonly name = 'CPYTOIMPF';
- public state: ComponentState = ComponentState.Installed;
- public currentVersion: number = 1;
-
- constructor(public connection: IBMi) { }
-
- async getInstalledVersion(): Promise {
- return 1;
- }
-
- async checkState(): Promise {
- return true;
- }
-
- getState(): ComponentState {
- return this.state;
- }
+import { ComponentState, IBMiComponent } from "./component";
+export class CopyToImport extends IBMiComponent {
static isSimple(statement: string): boolean {
statement = statement.trim();
if (statement.endsWith(';')) {
@@ -32,6 +13,18 @@ export class CopyToImport implements ComponentT {
return parts.length === 4 && parts[0].toUpperCase() === `SELECT` && parts[1] === `*` && parts[2].toUpperCase() === `FROM` && parts[3].includes(`.`);
}
+ getName() {
+ return 'CPYTOIMPF';
+ }
+
+ protected getRemoteState() {
+ return ComponentState.Installed;
+ }
+
+ protected update(): ComponentState | Promise {
+ return this.getRemoteState();
+ }
+
wrap(statement: string): WrapResult {
const outStmf = this.connection.getTempRemote(Tools.makeid())!;
diff --git a/src/components/getMemberInfo.ts b/src/components/getMemberInfo.ts
index 7ec9834c7..c78aeb258 100644
--- a/src/components/getMemberInfo.ts
+++ b/src/components/getMemberInfo.ts
@@ -1,107 +1,79 @@
import { posix } from "path";
-import IBMi from "../api/IBMi";
import { Tools } from "../api/Tools";
-import { instance } from "../instantiate";
import { IBMiMember } from "../typings";
-import { ComponentState, ComponentT } from "./component";
+import { ComponentState, IBMiComponent } from "./component";
-export class GetMemberInfo implements ComponentT {
- public readonly name = 'GETMBRINFO';
- public state: ComponentState = ComponentState.NotInstalled;
- public currentVersion: number = 1;
+export class GetMemberInfo extends IBMiComponent {
+ private readonly currentVersion = 1;
- constructor(public connection: IBMi) { }
+ getName() {
+ return "GETMBRINFO";
+ }
- async getInstalledVersion(): Promise {
- const config = this.connection.config!
- const lib = config.tempLibrary!;
- const sql = `select LONG_COMMENT from qsys2.sysroutines where routine_schema = '${lib.toUpperCase()}' and routine_name = '${this.name}'`
- const [result] = await this.connection.runSQL(sql);
- if (result && result.LONG_COMMENT) {
+ protected async getRemoteState(): Promise {
+ let installedVersion = 0;
+ const [result] = await this.connection.runSQL(`select LONG_COMMENT from qsys2.sysroutines where routine_schema = '${this.connection.config?.tempLibrary.toUpperCase()}' and routine_name = 'GETMBRINFO'`);
+ if (result.LONG_COMMENT) {
const comment = result.LONG_COMMENT as string;
const dash = comment.indexOf('-');
if (dash > -1) {
- const version = comment.substring(0, dash).trim();
- return parseInt(version);
+ installedVersion = Number(comment.substring(0, dash).trim());
}
}
-
- return 0;
- }
-
- async checkState(): Promise {
- const installedVersion = await this.getInstalledVersion();
-
- if (installedVersion === this.currentVersion) {
- this.state = ComponentState.Installed;
- return true;
+ if (installedVersion < this.currentVersion) {
+ return ComponentState.NeedUpdate;
}
- const config = this.connection.config!
- const content = instance.getContent();
+ return ComponentState.Installed;
+ }
+ protected async update() {
+ const config = this.connection.config!;
return this.connection.withTempDirectory(async tempDir => {
const tempSourcePath = posix.join(tempDir, `getMemberInfo.sql`);
-
- await content!.writeStreamfileRaw(tempSourcePath, getSource(config.tempLibrary, this.name, this.currentVersion));
+ await this.connection.content.writeStreamfileRaw(tempSourcePath, getSource(config.tempLibrary, this.getName(), this.currentVersion));
const result = await this.connection.runCommand({
command: `RUNSQLSTM SRCSTMF('${tempSourcePath}') COMMIT(*NONE) NAMING(*SQL)`,
cwd: `/`,
noLibList: true
});
- if (result.code === 0) {
- this.state = ComponentState.Installed;
+ if (result.code) {
+ return ComponentState.Error;
} else {
- this.state = ComponentState.Error;
+ return ComponentState.Installed;
}
-
- return this.state === ComponentState.Installed;
});
}
- getState(): ComponentState {
- return this.state;
- }
-
-
- /**
- *
- * @param filter: the criterias used to list the members
- * @returns
- */
async getMemberInfo(library: string, sourceFile: string, member: string): Promise {
- if (this.state === ComponentState.Installed) {
- const config = this.connection.config!;
- const tempLib = config.tempLibrary;
- const statement = `select * from table(${tempLib}.GETMBRINFO('${library}', '${sourceFile}', '${member}'))`;
-
- let results: Tools.DB2Row[] = [];
- if (config.enableSQL) {
- try {
- results = await this.connection.runSQL(statement);
- } catch (e) { }; // Ignore errors, will return undefined.
- }
- else {
- results = await this.connection.content.getQTempTable([`create table QTEMP.MEMBERINFO as (${statement}) with data`], "MEMBERINFO");
- }
+ const config = this.connection.config!;
+ const tempLib = config.tempLibrary;
+ const statement = `select * from table(${tempLib}.${this.getName()}('${library}', '${sourceFile}', '${member}'))`;
+
+ let results: Tools.DB2Row[] = [];
+ if (config.enableSQL) {
+ try {
+ results = await this.connection.runSQL(statement);
+ } catch (e) { } // Ignore errors, will return undefined.
+ }
+ else {
+ results = await this.connection.content.getQTempTable([`create table QTEMP.MEMBERINFO as (${statement}) with data`], "MEMBERINFO");
+ }
- if (results.length === 1 && results[0].ISSOURCE === 'Y') {
- const result = results[0];
- const asp = this.connection.aspInfo[Number(results[0].ASP)];
- return {
- library: result.LIBRARY,
- file: result.FILE,
- name: result.MEMBER,
- extension: result.EXTENSION,
- text: result.DESCRIPTION,
- created: new Date(result.CREATED ? Number(result.CREATED) : 0),
- changed: new Date(result.CHANGED ? Number(result.CHANGED) : 0)
- } as IBMiMember
- }
- else {
- return undefined;
- }
+ if (results.length === 1 && results[0].ISSOURCE === 'Y') {
+ const result = results[0];
+ const asp = this.connection.aspInfo[Number(results[0].ASP)];
+ return {
+ asp,
+ library: result.LIBRARY,
+ file: result.FILE,
+ name: result.MEMBER,
+ extension: result.EXTENSION,
+ text: result.DESCRIPTION,
+ created: new Date(result.CREATED ? Number(result.CREATED) : 0),
+ changed: new Date(result.CHANGED ? Number(result.CHANGED) : 0)
+ } as IBMiMember
}
}
}
diff --git a/src/components/getNewLibl.ts b/src/components/getNewLibl.ts
index 914d1dc51..06d0928a9 100644
--- a/src/components/getNewLibl.ts
+++ b/src/components/getNewLibl.ts
@@ -1,26 +1,19 @@
import { posix } from "path";
-import IBMi from "../api/IBMi";
import { instance } from "../instantiate";
-import { ComponentState, ComponentT } from "./component";
+import { ComponentState, IBMiComponent } from "./component";
-export class GetNewLibl implements ComponentT {
- public state: ComponentState = ComponentState.NotInstalled;
- public currentVersion: number = 1;
+export class GetNewLibl extends IBMiComponent {
+ private readonly currentVersion = 1;
- constructor(public connection: IBMi) { }
-
- async getInstalledVersion(): Promise {
- return (this.connection.remoteFeatures[`GETNEWLIBL.PGM`] ? 1 : 0);
+ getName() {
+ return 'GETNEWLIBL';
}
- async checkState(): Promise {
- const installedVersion = await this.getInstalledVersion();
-
- if (installedVersion === this.currentVersion) {
- this.state = ComponentState.Installed;
- return true;
- }
+ protected async getRemoteState() {
+ return this.connection.remoteFeatures[`GETNEWLIBL.PGM`] ? ComponentState.Installed : ComponentState.NotInstalled;
+ }
+ protected update() {
const config = this.connection.config!
const content = instance.getContent();
return this.connection.withTempDirectory(async tempDir => {
@@ -33,48 +26,37 @@ export class GetNewLibl implements ComponentT {
noLibList: true
});
- if (result.code === 0) {
- this.state = ComponentState.Installed;
+ if (!result.code) {
+ return ComponentState.Installed;
} else {
- this.state = ComponentState.Error;
+ return ComponentState.Error;
}
-
- return this.state === ComponentState.Installed;
});
}
- getState(): ComponentState {
- return this.state;
- }
-
- async getLibraryListFromCommand(ileCommand: string): Promise<{ currentLibrary: string; libraryList: string[]; } | undefined> {
- if (this.state === ComponentState.Installed) {
- const tempLib = this.connection.config!.tempLibrary;
- const resultSet = await this.connection.runSQL(`CALL ${tempLib}.GETNEWLIBL('${ileCommand.replace(new RegExp(`'`, 'g'), `''`)}')`);
-
- const result = {
- currentLibrary: `QGPL`,
- libraryList: [] as string[]
- };
-
- resultSet.forEach(row => {
- const libraryName = String(row.SYSTEM_SCHEMA_NAME);
- switch (row.PORTION) {
- case `CURRENT`:
- result.currentLibrary = libraryName;
- break;
- case `USER`:
- result.libraryList.push(libraryName);
- break;
- }
- })
-
- return result;
- }
+ async getLibraryListFromCommand(ileCommand: string) {
+ const tempLib = this.connection.config!.tempLibrary;
+ const resultSet = await this.connection.runSQL(`CALL ${tempLib}.GETNEWLIBL('${ileCommand.replace(new RegExp(`'`, 'g'), `''`)}')`);
+
+ const result = {
+ currentLibrary: `QGPL`,
+ libraryList: [] as string[]
+ };
+
+ resultSet.forEach(row => {
+ const libraryName = String(row.SYSTEM_SCHEMA_NAME);
+ switch (row.PORTION) {
+ case `CURRENT`:
+ result.currentLibrary = libraryName;
+ break;
+ case `USER`:
+ result.libraryList.push(libraryName);
+ break;
+ }
+ })
- return undefined;
+ return result;
}
-
}
function getSource(library: string) {
diff --git a/src/components/manager.ts b/src/components/manager.ts
index a34ccf12a..40fc00e59 100644
--- a/src/components/manager.ts
+++ b/src/components/manager.ts
@@ -1,13 +1,13 @@
import IBMi from "../api/IBMi";
-import { ComponentState, ComponentT } from "./component";
+import { ComponentState, IBMiComponent, IBMiComponentType } from "./component";
import { CopyToImport } from "./copyToImport";
import { GetMemberInfo } from "./getMemberInfo";
import { GetNewLibl } from "./getNewLibl";
export class ComponentRegistry {
- private readonly allComponents: (typeof ComponentT)[] = [GetNewLibl, CopyToImport, GetMemberInfo];
+ private readonly allComponents: (IBMiComponentType)[] = [GetNewLibl, CopyToImport, GetMemberInfo];
- public registerComponent(component: typeof ComponentT) {
+ public registerComponent(component: IBMiComponentType) {
this.allComponents.push(component);
}
@@ -19,29 +19,22 @@ export class ComponentRegistry {
export const extensionComponentRegistry = new ComponentRegistry();
export class ComponentManager {
- private readonly registered: Map = new Map;
+ private readonly registered: Map, IBMiComponent> = new Map;
- public async startup(connection: IBMi) {
+ constructor(private readonly connection: IBMi) {
+
+ }
+
+ public async startup() {
for (const Component of extensionComponentRegistry.getComponents()) {
- const instance = new Component(connection);
- this.registered.set(Component.name, instance);
- await ComponentManager.checkState(instance);
+ this.registered.set(Component, await new Component(this.connection).check());
}
}
- get(id: string): T | undefined {
- const component = this.registered.get(id);
+ get(type: IBMiComponentType): T | undefined {
+ const component = this.registered.get(type);
if (component && component.getState() === ComponentState.Installed) {
return component as T;
}
}
-
- private static async checkState(component: ComponentT) {
- try {
- await component.checkState();
- } catch (e) {
- console.log(component);
- console.log(`Error checking state for ${component.constructor.name}`, e);
- }
- }
}
\ No newline at end of file
diff --git a/src/instantiate.ts b/src/instantiate.ts
index dd90c0c62..b74d1b1e4 100644
--- a/src/instantiate.ts
+++ b/src/instantiate.ts
@@ -466,25 +466,25 @@ export async function loadAllofExtension(context: vscode.ExtensionContext) {
const selectionSplit = connection!.upperCaseName(selection).split('/')
if (selectionSplit.length === 3 || selection.startsWith(`/`)) {
- const infoComponent = connection?.getComponent(`GetMemberInfo`);
+ const infoComponent = connection?.getComponent(GetMemberInfo);
// When selection is QSYS path
- if (!selection.startsWith(`/`) && infoComponent) {
- const lib = selectionSplit[0];
+ if (!selection.startsWith(`/`) && infoComponent && connection) {
+ const library = selectionSplit[0];
const file = selectionSplit[1];
const member = path.parse(selectionSplit[2]);
member.ext = member.ext.substring(1);
- const memberInfo = await infoComponent.getMemberInfo(lib, file, member.name);
+ const memberInfo = await infoComponent.getMemberInfo(library, file, member.name);
if (!memberInfo) {
- vscode.window.showWarningMessage(`Source member ${lib}/${file}/${member.base} does not exist.`);
+ vscode.window.showWarningMessage(`Source member ${library}/${file}/${member.base} does not exist.`);
return;
} else if (memberInfo.name !== member.name || (member.ext && memberInfo.extension !== member.ext)) {
- vscode.window.showWarningMessage(`Member ${lib}/${file}/${member.name} of type ${member.ext} does not exist.`);
+ vscode.window.showWarningMessage(`Member ${library}/${file}/${member.name} of type ${member.ext} does not exist.`);
return;
}
member.base = `${member.name}.${member.ext || memberInfo.extension}`;
- selection = `${lib}/${file}/${member.base}`;
+ selection = `${library}/${file}/${member.base}`;
};
// When select is IFS path
diff --git a/src/testing/components.ts b/src/testing/components.ts
index 491351a83..0893c70a8 100644
--- a/src/testing/components.ts
+++ b/src/testing/components.ts
@@ -16,7 +16,7 @@ export const ComponentSuite: TestSuite = {
{
name: `Get new libl`, test: async () => {
const connection = instance.getConnection()!
- const component = connection.getComponent(`GetNewLibl`);
+ const component = connection.getComponent(GetNewLibl);
if (component) {
const newLibl = await component.getLibraryListFromCommand(`CHGLIBL CURLIB(SYSTOOLS)`);
@@ -31,7 +31,7 @@ export const ComponentSuite: TestSuite = {
{
name: `Check getMemberInfo`, test: async () => {
const connection = instance.getConnection();
- const component = connection?.getComponent(`GetMemberInfo`)!;
+ const component = connection?.getComponent(GetMemberInfo)!;
assert.ok(component);
diff --git a/src/views/ProfilesView.ts b/src/views/ProfilesView.ts
index f9c6e00be..c77005c93 100644
--- a/src/views/ProfilesView.ts
+++ b/src/views/ProfilesView.ts
@@ -121,7 +121,7 @@ export class ProfilesView {
if (storedProfile) {
try {
- const component = connection?.getComponent(`GetNewLibl`)
+ const component = connection?.getComponent(GetNewLibl)
const newSettings = await component?.getLibraryListFromCommand(storedProfile.command);
if (newSettings) {
From 5e5e0af4a674c6f38d69954d74b34b39c01c5a28 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Thu, 19 Sep 2024 11:12:02 +0200
Subject: [PATCH 07/28] Require context to register a component + display
components list
Signed-off-by: Seb Julliand
---
src/api/IBMi.ts | 4 ++--
src/components/component.ts | 18 ++++++++++--------
src/components/manager.ts | 26 ++++++++++++++++----------
src/extension.ts | 19 +++++++++++++------
src/webviews/settings/index.ts | 23 +++++++++++++++++++++--
5 files changed, 62 insertions(+), 28 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index d1f713f55..29c923057 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -1354,8 +1354,8 @@ export default class IBMi {
}
}
- getComponent(type: IBMiComponentType): T | undefined {
- return this.componentManager.get(type);
+ getComponent(type: IBMiComponentType, ignoreState?:boolean): T | undefined {
+ return this.componentManager.get(type, ignoreState);
}
/**
diff --git a/src/components/component.ts b/src/components/component.ts
index f7ca0b92d..7ed33838b 100644
--- a/src/components/component.ts
+++ b/src/components/component.ts
@@ -1,10 +1,10 @@
import IBMi from "../api/IBMi";
export const enum ComponentState {
- NotChecked = `NotChecked`,
- NotInstalled = `NotInstalled`,
+ NotChecked = `Not checked`,
+ NotInstalled = `Not installed`,
Installed = `Installed`,
- NeedUpdate = `NeedUpdate`,
+ NeedUpdate = `Need update`,
Error = `Error`,
}
export type IBMiComponentType = new (c: IBMi) => T;
@@ -20,12 +20,14 @@ export type IBMiComponentType = new (c: IBMi) => T;
* //implements getName(), getRemoteState() and update()
* }
* ```
- * Must be registered like this:
+ * Must be registered like this, when the extension providing the component gets activated:
* ```
- * const codeForIBMiExtension = vscode.extensions.getExtension('halcyontechltd.code-for-ibmi');
- * if (codeForIBMiExtension) {
- * codeForIBMi = codeForIBMiExtension.isActive ? codeForIBMiExtension.exports : await codeForIBMiExtension.activate();
- * codeForIBMi.componentRegistry.registerComponent(MyIBMIComponent);
+ * export async function activate(context: ExtensionContext) {
+ * const codeForIBMiExtension = vscode.extensions.getExtension('halcyontechltd.code-for-ibmi');
+ * if (codeForIBMiExtension) {
+ * codeForIBMi = codeForIBMiExtension.isActive ? codeForIBMiExtension.exports : await codeForIBMiExtension.activate();
+ * codeForIBMi.componentRegistry.registerComponent(context, MyIBMIComponent);
+ * }
* }
* ```
*
diff --git a/src/components/manager.ts b/src/components/manager.ts
index 40fc00e59..ec0c19152 100644
--- a/src/components/manager.ts
+++ b/src/components/manager.ts
@@ -1,18 +1,23 @@
+import vscode from "vscode";
import IBMi from "../api/IBMi";
import { ComponentState, IBMiComponent, IBMiComponentType } from "./component";
-import { CopyToImport } from "./copyToImport";
-import { GetMemberInfo } from "./getMemberInfo";
-import { GetNewLibl } from "./getNewLibl";
export class ComponentRegistry {
- private readonly allComponents: (IBMiComponentType)[] = [GetNewLibl, CopyToImport, GetMemberInfo];
+ private readonly components: Map)[]> = new Map;
- public registerComponent(component: IBMiComponentType) {
- this.allComponents.push(component);
+ public registerComponent(context: vscode.ExtensionContext, component: IBMiComponentType) {
+ const key = context.extension.id;
+ const extensionComponents = this.components.get(key);
+ if (extensionComponents) {
+ extensionComponents.push(component);
+ }
+ else {
+ this.components.set(key, [component]);
+ }
}
public getComponents() {
- return this.allComponents;
+ return this.components;
}
}
@@ -26,14 +31,15 @@ export class ComponentManager {
}
public async startup() {
- for (const Component of extensionComponentRegistry.getComponents()) {
+ const components = Array.from(extensionComponentRegistry.getComponents().values()).flatMap(a => a.flat());
+ for (const Component of components) {
this.registered.set(Component, await new Component(this.connection).check());
}
}
- get(type: IBMiComponentType): T | undefined {
+ get(type: IBMiComponentType, ignoreState?: boolean): T | undefined {
const component = this.registered.get(type);
- if (component && component.getState() === ComponentState.Installed) {
+ if (component && (ignoreState || component.getState() === ComponentState.Installed)) {
return component as T;
}
}
diff --git a/src/extension.ts b/src/extension.ts
index 0f0965f4d..f4e04ab97 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -16,6 +16,9 @@ import * as Debug from './api/debug';
import { parseErrors } from "./api/errors/parser";
import { DeployTools } from "./api/local/deployTools";
import { Deployment } from "./api/local/deployment";
+import { CopyToImport } from "./components/copyToImport";
+import { GetMemberInfo } from "./components/getMemberInfo";
+import { GetNewLibl } from "./components/getNewLibl";
import { extensionComponentRegistry } from "./components/manager";
import { IFSFS } from "./filesystems/ifsFs";
import { LocalActionCompletionItemProvider } from "./languages/actions/completion";
@@ -126,12 +129,16 @@ export async function activate(context: ExtensionContext): Promise
commands.executeCommand("code-for-ibmi.refreshProfileView");
});
- return {
- instance, customUI: () => new CustomUI(),
- deployTools: DeployTools,
- evfeventParser: parseErrors,
- tools: Tools,
- componentRegistry: extensionComponentRegistry
+ extensionComponentRegistry.registerComponent(context, GetNewLibl);
+ extensionComponentRegistry.registerComponent(context, GetMemberInfo);
+ extensionComponentRegistry.registerComponent(context, CopyToImport);
+
+ return {
+ instance, customUI: () => new CustomUI(),
+ deployTools: DeployTools,
+ evfeventParser: parseErrors,
+ tools: Tools,
+ componentRegistry: extensionComponentRegistry
};
}
diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts
index 7694d390d..80b7e4130 100644
--- a/src/webviews/settings/index.ts
+++ b/src/webviews/settings/index.ts
@@ -6,6 +6,8 @@ import { Tools } from "../../api/Tools";
import { isManaged } from "../../api/debug";
import * as certificates from "../../api/debug/certificates";
import { isSEPSupported } from "../../api/debug/server";
+import { IBMiComponent } from "../../components/component";
+import { extensionComponentRegistry } from "../../components/manager";
import { instance } from "../../instantiate";
import { t } from "../../locale";
import { ConnectionData, Server } from '../../typings';
@@ -215,12 +217,29 @@ export class SettingsUI {
debuggerTab.addParagraph('Connect to the server to see these settings.');
}
- let tabs: ComplexTab[] = [
+ const componentsTab = new Section();
+ if (connection) {
+ componentsTab.addParagraph(`The following extensions contribute these components:`);
+ extensionComponentRegistry.getComponents().forEach((components, extensionId) => {
+ const extension = vscode.extensions.getExtension(extensionId);
+ componentsTab.addParagraph(`
+
${extension?.packageJSON.displayName || extension?.id || "Unnamed extension"}
+
+ ${components.map(type => connection.getComponent(type, true)).map(component => `${component?.getName()}
: ${component?.getState()} `).join(``)}
+
+
`);
+ })
+ } else {
+ componentsTab.addParagraph('Connect to the server to see these settings.');
+ }
+
+ const tabs: ComplexTab[] = [
{ label: `Features`, fields: featuresTab.fields },
{ label: `Source Code`, fields: sourceTab.fields },
{ label: `Terminals`, fields: terminalsTab.fields },
{ label: `Debugger`, fields: debuggerTab.fields },
{ label: `Temporary Data`, fields: tempDataTab.fields },
+ { label: `Components`, fields: componentsTab.fields },
];
const ui = new CustomUI();
@@ -380,7 +399,7 @@ export class SettingsUI {
await ConnectionManager.deleteStoredPassword(context, name);
vscode.window.showInformationMessage(t(`login.privateKey.updated`, name));
}
- else{
+ else {
delete data.privateKeyPath;
}
break;
From 56427caef3c89ea28899c6da5b51b783dcd9a5b7 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Thu, 19 Sep 2024 15:29:27 +0200
Subject: [PATCH 08/28] Show version of each component
Signed-off-by: Seb Julliand
---
src/components/component.ts | 15 ++++++-
src/components/copyToImport.ts | 4 +-
src/components/getMemberInfo.ts | 80 ++++++++++++++++-----------------
src/components/getNewLibl.ts | 6 +--
src/webviews/settings/index.ts | 2 +-
5 files changed, 56 insertions(+), 51 deletions(-)
diff --git a/src/components/component.ts b/src/components/component.ts
index 7ed33838b..0612c6f11 100644
--- a/src/components/component.ts
+++ b/src/components/component.ts
@@ -7,6 +7,12 @@ export const enum ComponentState {
NeedUpdate = `Need update`,
Error = `Error`,
}
+
+export type ComponentIdentification = {
+ name: string
+ version: number
+}
+
export type IBMiComponentType = new (c: IBMi) => T;
/**
@@ -51,7 +57,7 @@ export abstract class IBMiComponent {
}
}
catch (error) {
- console.log(`Error occurred while checking component ${this.getName()}`);
+ console.log(`Error occurred while checking component ${this.toString()}`);
console.log(error);
this.state = ComponentState.Error;
}
@@ -59,12 +65,17 @@ export abstract class IBMiComponent {
return this;
}
+ toString() {
+ const identification = this.getIdentification();
+ return `${identification.name} (version ${identification.version})`
+ }
+
/**
* The name of this component; mainly used for display and logging purposes
*
* @returns a human-readable name
*/
- abstract getName(): string;
+ abstract getIdentification(): ComponentIdentification;
/**
* @returns the component's {@link ComponentState state} on the IBM i
diff --git a/src/components/copyToImport.ts b/src/components/copyToImport.ts
index 24b2d0458..7232acc18 100644
--- a/src/components/copyToImport.ts
+++ b/src/components/copyToImport.ts
@@ -13,8 +13,8 @@ export class CopyToImport extends IBMiComponent {
return parts.length === 4 && parts[0].toUpperCase() === `SELECT` && parts[1] === `*` && parts[2].toUpperCase() === `FROM` && parts[3].includes(`.`);
}
- getName() {
- return 'CPYTOIMPF';
+ getIdentification() {
+ return { name: 'CopyToImport', version: 1 };
}
protected getRemoteState() {
diff --git a/src/components/getMemberInfo.ts b/src/components/getMemberInfo.ts
index 1d1b7126c..2f86720e2 100644
--- a/src/components/getMemberInfo.ts
+++ b/src/components/getMemberInfo.ts
@@ -4,23 +4,24 @@ import { IBMiMember } from "../typings";
import { ComponentState, IBMiComponent } from "./component";
export class GetMemberInfo extends IBMiComponent {
+ private readonly procedureName = 'GETMBRINFO';
private readonly currentVersion = 1;
+ private installedVersion = 0;
- getName() {
- return "GETMBRINFO";
+ getIdentification() {
+ return { name: 'GetMemberInfo', version: this.installedVersion };
}
protected async getRemoteState(): Promise {
- let installedVersion = 0;
- const [result] = await this.connection.runSQL(`select LONG_COMMENT from qsys2.sysroutines where routine_schema = '${this.connection.config?.tempLibrary.toUpperCase()}' and routine_name = 'GETMBRINFO'`);
+ const [result] = await this.connection.runSQL(`select LONG_COMMENT from qsys2.sysroutines where routine_schema = '${this.connection.config?.tempLibrary.toUpperCase()}' and routine_name = '${this.procedureName}'`);
if (result.LONG_COMMENT) {
const comment = result.LONG_COMMENT as string;
const dash = comment.indexOf('-');
if (dash > -1) {
- installedVersion = Number(comment.substring(0, dash).trim());
+ this.installedVersion = Number(comment.substring(0, dash).trim());
}
}
- if (installedVersion < this.currentVersion) {
+ if (this.installedVersion < this.currentVersion) {
return ComponentState.NeedUpdate;
}
@@ -31,7 +32,7 @@ export class GetMemberInfo extends IBMiComponent {
const config = this.connection.config!;
return this.connection.withTempDirectory(async tempDir => {
const tempSourcePath = posix.join(tempDir, `getMemberInfo.sql`);
- await this.connection.content.writeStreamfileRaw(tempSourcePath, getSource(config.tempLibrary, this.getName(), this.currentVersion));
+ await this.connection.content.writeStreamfileRaw(tempSourcePath, getSource(config.tempLibrary, this.procedureName, this.currentVersion));
const result = await this.connection.runCommand({
command: `RUNSQLSTM SRCSTMF('${tempSourcePath}') COMMIT(*NONE) NAMING(*SQL)`,
cwd: `/`,
@@ -49,7 +50,7 @@ export class GetMemberInfo extends IBMiComponent {
async getMemberInfo(library: string, sourceFile: string, member: string): Promise {
const config = this.connection.config!;
const tempLib = config.tempLibrary;
- const statement = `select * from table(${tempLib}.${this.getName()}('${library}', '${sourceFile}', '${member}'))`;
+ const statement = `select * from table(${tempLib}.${this.procedureName}('${library}', '${sourceFile}', '${member}'))`;
let results: Tools.DB2Row[] = [];
if (config.enableSQL) {
@@ -77,41 +78,36 @@ export class GetMemberInfo extends IBMiComponent {
}
}
- async getMultipleMemberInfo(members: IBMiMember[]): Promise {
- if (this.state === ComponentState.Installed) {
- const config = this.connection.config!;
- const tempLib = config.tempLibrary;
- const statement = members
- .map(member => `select * from table(${this.connection.config!.tempLibrary}.GETMBRINFO('${member.library}', '${member.file}', '${member.name}'))`)
- .join(' union all ');
-
- let results: Tools.DB2Row[] = [];
- if (config.enableSQL) {
- try {
- results = await this.connection.runSQL(statement);
- } catch (e) { }; // Ignore errors, will return undefined.
- }
- else {
- results = await this.connection.content.getQTempTable([`create table QTEMP.MEMBERINFO as (${statement}) with data`], "MEMBERINFO");
- }
-
- return results.filter(row => row.ISSOURCE === 'Y').map(result => {
- const asp = this.connection.aspInfo[Number(result.ASP)];
- return {
- asp,
- library: result.LIBRARY,
- file: result.FILE,
- name: result.MEMBER,
- extension: result.EXTENSION,
- text: result.DESCRIPTION,
- created: new Date(result.CREATED ? Number(result.CREATED) : 0),
- changed: new Date(result.CHANGED ? Number(result.CHANGED) : 0)
- } as IBMiMember
- });
+ async getMultipleMemberInfo(members: IBMiMember[]): Promise {
+ const config = this.connection.config!;
+ const tempLib = config.tempLibrary;
+ const statement = members
+ .map(member => `select * from table(${tempLib}.${this.procedureName}('${member.library}', '${member.file}', '${member.name}'))`)
+ .join(' union all ');
- } else {
- return undefined;
+ let results: Tools.DB2Row[] = [];
+ if (config.enableSQL) {
+ try {
+ results = await this.connection.runSQL(statement);
+ } catch (e) { }; // Ignore errors, will return undefined.
+ }
+ else {
+ results = await this.connection.content.getQTempTable([`create table QTEMP.MEMBERINFO as (${statement}) with data`], "MEMBERINFO");
}
+
+ return results.filter(row => row.ISSOURCE === 'Y').map(result => {
+ const asp = this.connection.aspInfo[Number(result.ASP)];
+ return {
+ asp,
+ library: result.LIBRARY,
+ file: result.FILE,
+ name: result.MEMBER,
+ extension: result.EXTENSION,
+ text: result.DESCRIPTION,
+ created: new Date(result.CREATED ? Number(result.CREATED) : 0),
+ changed: new Date(result.CHANGED ? Number(result.CHANGED) : 0)
+ } as IBMiMember
+ });
}
}
@@ -142,7 +138,7 @@ function getSource(library: string, name: string, version: number) {
`, Description varchar( 50 )`,
`, isSource char( 1 )`,
`)`,
- `specific GETMBRINFO`,
+ `specific ${name}`,
`modifies sql data`,
`begin`,
` declare buffer char( 135 ) for bit data not null default '';`,
diff --git a/src/components/getNewLibl.ts b/src/components/getNewLibl.ts
index 06d0928a9..64014eef7 100644
--- a/src/components/getNewLibl.ts
+++ b/src/components/getNewLibl.ts
@@ -3,10 +3,8 @@ import { instance } from "../instantiate";
import { ComponentState, IBMiComponent } from "./component";
export class GetNewLibl extends IBMiComponent {
- private readonly currentVersion = 1;
-
- getName() {
- return 'GETNEWLIBL';
+ getIdentification() {
+ return { name: 'GetNewLibl', version: 1 };
}
protected async getRemoteState() {
diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts
index 80b7e4130..e3d660b5c 100644
--- a/src/webviews/settings/index.ts
+++ b/src/webviews/settings/index.ts
@@ -225,7 +225,7 @@ export class SettingsUI {
componentsTab.addParagraph(`
${extension?.packageJSON.displayName || extension?.id || "Unnamed extension"}
- ${components.map(type => connection.getComponent(type, true)).map(component => `${component?.getName()}
: ${component?.getState()} `).join(``)}
+ ${components.map(type => connection.getComponent(type, true)).map(component => `${component?.toString()}
: ${component?.getState()} `).join(``)}
`);
})
From cee0a5486ffac43512ebd68a6ebea0abb258949e Mon Sep 17 00:00:00 2001
From: krethan
Date: Thu, 26 Sep 2024 23:15:13 -0600
Subject: [PATCH 09/28] Seperating logic from IBMi.ts into a new class.
---
src/api/IBMi.ts | 550 ++++++++++------------------------------
src/api/IBMiApps.ts | 87 +++++++
src/api/IBMiSettings.ts | 515 +++++++++++++++++++++++++++++++++++++
src/typings.ts | 21 +-
4 files changed, 757 insertions(+), 416 deletions(-)
create mode 100644 src/api/IBMiApps.ts
create mode 100644 src/api/IBMiSettings.ts
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index fe2efe28b..3c250b276 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -9,7 +9,7 @@ import path from 'path';
import { ComponentId, ComponentManager } from "../components/component";
import { CopyToImport } from "../components/copyToImport";
import { instance } from "../instantiate";
-import { CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand, SpecialAuthorities, WrapResult } from "../typings";
+import { CommandData, CommandResult, ConnectionData, MemberParts, RemoteCommand, RemoteFeatures, SpecialAuthorities, WrapResult } from "../typings";
import { CompileTools } from "./CompileTools";
import IBMiContent from "./IBMiContent";
import { CachedServerSettings, GlobalStorage } from './Storage';
@@ -17,38 +17,12 @@ import { Tools } from './Tools';
import * as configVars from './configVars';
import { DebugConfiguration } from "./debug/config";
import { debugPTFInstalled } from "./debug/server";
-
-export interface MemberParts extends IBMiMember {
- basename: string
-}
+import IBMiSettings from "./IBMiSettings";
+import IBMiApps from "./IBMiApps";
const CCSID_SYSVAL = -2;
const bashShellPath = '/QOpenSys/pkgs/bin/bash';
-const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures' below!!
- {
- path: `/usr/bin/`,
- names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
- },
- {
- path: `/QOpenSys/pkgs/bin/`,
- names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
- },
- {
- path: `/QSYS.LIB/`,
- // In the future, we may use a generic specific.
- // Right now we only need one program
- // specific: `*.PGM`,
- specific: `QZDFMDB2.PGM`,
- names: [`QZDFMDB2.PGM`]
- },
- {
- path: `/QIBM/ProdData/IBMiDebugService/bin/`,
- specific: `startDebugService.sh`,
- names: [`startDebugService.sh`]
- }
-];
-
export default class IBMi {
private qccsid: number = 65535;
private jobCcsid: number = CCSID_SYSVAL;
@@ -72,7 +46,7 @@ export default class IBMi {
* the root of the IFS, thus why we store it.
*/
aspInfo: { [id: number]: string } = {};
- remoteFeatures: { [name: string]: string | undefined };
+ remoteFeatures: RemoteFeatures;
variantChars: { american: string, local: string };
/**
@@ -94,26 +68,7 @@ export default class IBMi {
constructor() {
this.client = new node_ssh.NodeSSH;
- this.remoteFeatures = {
- git: undefined,
- grep: undefined,
- tn5250: undefined,
- setccsid: undefined,
- md5sum: undefined,
- bash: undefined,
- chsh: undefined,
- stat: undefined,
- sort: undefined,
- 'GETNEWLIBL.PGM': undefined,
- 'GETMBRINFO.SQL': undefined,
- 'QZDFMDB2.PGM': undefined,
- 'startDebugService.sh': undefined,
- attr: undefined,
- iconv: undefined,
- tar: undefined,
- ls: undefined,
- find: undefined,
- };
+ this.remoteFeatures = {};
this.variantChars = {
american: `#@$`,
@@ -191,17 +146,17 @@ export default class IBMi {
// Reload server settings?
const quickConnect = (this.config.quickConnect === true && reloadServerSettings === false);
+ //Initialize IBMiConnectionSettings to be used throughout to get settings
+ let connSettings = new IBMiSettings(this);
+
// Check shell output for additional user text - this will confuse Code...
progress.report({
message: `Checking shell output.`
});
- const checkShellText = `This should be the only text!`;
- const checkShellResult = await this.sendCommand({
- command: `echo "${checkShellText}"`,
- directory: `.`
- });
- if (checkShellResult.stdout.split(`\n`)[0] !== checkShellText) {
+ const checkShellResult = await connSettings.checkShellOutput();
+
+ if (!checkShellResult) {
const chosen = await vscode.window.showErrorMessage(`Error in shell configuration!`, {
detail: [
`This extension can not work with the shell configured on ${this.currentConnectionName},`,
@@ -233,74 +188,37 @@ export default class IBMi {
message: `Checking home directory.`
});
- let defaultHomeDir;
-
- const echoHomeResult = await this.sendCommand({
- command: `echo $HOME && cd && test -w $HOME`,
- directory: `.`
- });
- // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
- // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
- // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
- // if we iterate on this code again in the future)
- // - stdout contains the name of the home directory (even if it does not exist)
- // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
- // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
- let isHomeUsable = (0 == echoHomeResult.code);
- if (isHomeUsable) {
- defaultHomeDir = echoHomeResult.stdout.trim();
- } else {
- // Let's try to provide more valuable information to the user about why their home directory
- // is bad and maybe even provide the opportunity to create the home directory
-
- let actualHomeDir = echoHomeResult.stdout.trim();
-
- // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
- let doesHomeExist = (0 === (await this.sendCommand({ command: `test -e ${actualHomeDir}` })).code);
- if (doesHomeExist) {
- // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
- // But, remember, but we only got here if 'cd $HOME' failed.
- // Let's try to figure out why....
- if (0 !== (await this.sendCommand({ command: `test -d ${actualHomeDir}` })).code) {
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
- }
- else if (0 !== (await this.sendCommand({ command: `test -w ${actualHomeDir}` })).code) {
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
- }
- else if (0 !== (await this.sendCommand({ command: `test -x ${actualHomeDir}` })).code) {
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
+ const homeResult = await connSettings.getHomeDirectory();
+ if (homeResult.homeMsg) {
+ if (homeResult.homeExists) {
+ //Home Directory exists but give informational message
+ await vscode.window.showWarningMessage(homeResult.homeMsg, { modal: !reconnecting });
+ }
+ else {
+ //Home Directory does not exist
+ if (reconnecting) {
+ vscode.window.showWarningMessage(homeResult.homeMsg, { modal: false });
}
else {
- // not sure, but get your sys admin involved
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
- }
- }
- else if (reconnecting) {
- vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) does not exist. Code for IBM i may not function correctly.`, { modal: false });
- }
- else if (await vscode.window.showWarningMessage(`Home directory does not exist`, {
- modal: true,
- detail: `Your home directory (${actualHomeDir}) does not exist, so Code for IBM i may not function correctly. Would you like to create this directory now?`,
- }, `Yes`)) {
- this.appendOutput(`creating home directory ${actualHomeDir}`);
- let mkHomeCmd = `mkdir -p ${actualHomeDir} && chown ${connectionObject.username.toLowerCase()} ${actualHomeDir} && chmod 0755 ${actualHomeDir}`;
- let mkHomeResult = await this.sendCommand({ command: mkHomeCmd, directory: `.` });
- if (0 === mkHomeResult.code) {
- defaultHomeDir = actualHomeDir;
- } else {
- let mkHomeErrs = mkHomeResult.stderr;
- // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
- mkHomeErrs = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
- await vscode.window.showWarningMessage(`Error creating home directory (${actualHomeDir}):\n${mkHomeErrs}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
+ if (await vscode.window.showWarningMessage(`Home directory does not exist`, {
+ modal: true,
+ detail: `Your home directory (${homeResult.homeDir}) does not exist, so Code for IBM i may not function correctly. Would you like to create this directory now?`,
+ }, `Yes`)) {
+ this.appendOutput(`creating home directory ${homeResult.homeDir}`);
+ let homeCreatedResult = await connSettings.createHomeDirectory(homeResult.homeDir, connectionObject.username);
+ if (!homeCreatedResult.homeCreated) {
+ await vscode.window.showWarningMessage(homeCreatedResult.homeMsg, { modal: true });
+ }
+ }
}
}
}
// Check to see if we need to store a new value for the home directory
- if (defaultHomeDir) {
- if (this.config.homeDirectory !== defaultHomeDir) {
- this.config.homeDirectory = defaultHomeDir;
- vscode.window.showInformationMessage(`Configured home directory reset to ${defaultHomeDir}.`);
+ if (homeResult.homeDir) {
+ if (this.config.homeDirectory !== homeResult.homeDir) {
+ this.config.homeDirectory = homeResult.homeDir;
+ vscode.window.showInformationMessage(`Configured home directory reset to ${homeResult.homeDir}.`);
}
} else {
// New connections always have `.` as the initial value.
@@ -311,7 +229,7 @@ export default class IBMi {
//Set a default IFS listing
if (this.config.ifsShortcuts.length === 0) {
- if (defaultHomeDir) {
+ if (homeResult.homeDir) {
this.config.ifsShortcuts = [this.config.homeDirectory];
} else {
this.config.ifsShortcuts = [`/`];
@@ -322,42 +240,19 @@ export default class IBMi {
message: `Checking library list configuration.`
});
- //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
- //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
- let currentLibrary = `QGPL`;
this.defaultUserLibraries = [];
- const liblResult = await this.sendQsh({
- command: `liblist`
- });
- if (liblResult.code === 0) {
- const libraryListString = liblResult.stdout;
- if (libraryListString !== ``) {
- const libraryList = libraryListString.split(`\n`);
-
- let lib, type;
- for (const line of libraryList) {
- lib = line.substring(0, 10).trim();
- type = line.substring(12);
-
- switch (type) {
- case `USR`:
- this.defaultUserLibraries.push(lib);
- break;
-
- case `CUR`:
- currentLibrary = lib;
- break;
- }
- }
+ let libraryListResult = await connSettings.getLibraryList();
+ if (libraryListResult.libStatus) {
+
+ this.defaultUserLibraries = libraryListResult.defaultUserLibraries;
- //If this is the first time the config is made, then these arrays will be empty
- if (this.config.currentLibrary.length === 0) {
- this.config.currentLibrary = currentLibrary;
- }
- if (this.config.libraryList.length === 0) {
- this.config.libraryList = this.defaultUserLibraries;
- }
+ //If this is the first time the config is made, then these arrays will be empty
+ if (this.config.currentLibrary.length === 0) {
+ this.config.currentLibrary = libraryListResult.currentLibrary;
+ }
+ if (this.config.libraryList.length === 0) {
+ this.config.libraryList = libraryListResult.defaultUserLibraries;
}
}
@@ -366,60 +261,20 @@ export default class IBMi {
});
//Next, we need to check the temp lib (where temp outfile data lives) exists
- const createdTempLib = await this.runCommand({
- command: `CRTLIB LIB(${this.config.tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
- noLibList: true
- });
-
- if (createdTempLib.code === 0) {
- tempLibrarySet = true;
- } else {
- const messages = Tools.parseMessages(createdTempLib.stderr);
- if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
+ tempLibrarySet = await connSettings.setTempLibrary(this.config.tempLibrary);
+ if (!tempLibrarySet) {
+ if (libraryListResult.currentLibrary && !libraryListResult.currentLibrary.startsWith(`Q`)) {
+ //Using ${currentLibrary} as the temporary library for temporary data.
+ this.config.tempLibrary = this.config.currentLibrary;
tempLibrarySet = true;
}
- else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
- const tempLibExists = await this.runCommand({
- command: `CHKOBJ OBJ(QSYS/${this.config.tempLibrary}) OBJTYPE(*LIB)`,
- noLibList: true
- });
-
- if (tempLibExists.code === 0) {
- //We're all good if no errors
- tempLibrarySet = true;
- } else if (currentLibrary && !currentLibrary.startsWith(`Q`)) {
- //Using ${currentLibrary} as the temporary library for temporary data.
- this.config.tempLibrary = currentLibrary;
- tempLibrarySet = true;
- }
- }
}
progress.report({
message: `Checking temporary directory configuration.`
});
- let tempDirSet = false;
- // Next, we need to check if the temp directory exists
- let result = await this.sendCommand({
- command: `[ -d "${this.config.tempDir}" ]`
- });
-
- if (result.code === 0) {
- // Directory exists
- tempDirSet = true;
- } else {
- // Directory does not exist, try to create it
- let result = await this.sendCommand({
- command: `mkdir -p ${this.config.tempDir}`
- });
- if (result.code === 0) {
- // Directory created
- tempDirSet = true;
- } else {
- // Directory not created
- }
- }
+ let tempDirSet = await connSettings.setTempDirectory(this.config.tempDir);
if (!tempDirSet) {
this.config.tempDir = `/tmp`;
@@ -429,47 +284,38 @@ export default class IBMi {
progress.report({
message: `Clearing temporary data.`
});
-
- this.runCommand({
- command: `DLTOBJ OBJ(${this.config.tempLibrary}/O_*) OBJTYPE(*FILE)`,
- noLibList: true,
- })
- .then(result => {
- // All good!
- if (result && result.stderr) {
- const messages = Tools.parseMessages(result.stderr);
- if (!messages.findId(`CPF2125`)) {
- // @ts-ignore We know the config exists.
- vscode.window.showErrorMessage(`Temporary data not cleared from ${this.config.tempLibrary}.`, `View log`).then(async choice => {
- if (choice === `View log`) {
- this.outputChannel!.show();
- }
- });
+ //Clear Temporary Library Data
+ let clearMsg = await connSettings.clearTempLibrary(this.config.tempLibrary);
+ if (clearMsg) {
+ // @ts-ignore We know the config exists.
+ vscode.window.showErrorMessage(clearMsg, `View log`).then
+ (async choice => {
+ if (choice === `View log`) {
+ this.outputChannel!.show();
}
- }
- })
-
- this.sendCommand({
- command: `rm -rf ${path.posix.join(this.config.tempDir, `vscodetemp*`)}`
- })
- .then(result => {
- // All good!
- })
- .catch(e => {
- // CPF2125: No objects deleted.
- // @ts-ignore We know the config exists.
- vscode.window.showErrorMessage(`Temporary data not cleared from ${this.config.tempDir}.`, `View log`).then(async choice => {
+ });
+ }
+
+ //Clear Temporary Directory Data
+ clearMsg = await connSettings.clearTempDirectory(this.config.tempDir);
+ if (clearMsg) {
+ // @ts-ignore We know the config exists.
+ vscode.window.showErrorMessage(clearMsg, `View log`).then
+ (async choice => {
if (choice === `View log`) {
this.outputChannel!.show();
}
});
- });
+ }
+
}
+ //TO DO: why is this required????
const commandShellResult = await this.sendCommand({
command: `echo $SHELL`
});
+ //TO DO: why is this required????
if (commandShellResult.code === 0) {
this.shell = commandShellResult.stdout.trim();
}
@@ -482,29 +328,26 @@ export default class IBMi {
message: `Checking for bad data areas.`
});
- const QCPTOIMPF = await this.runCommand({
- command: `CHKOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- });
+ const QCPTOIMPF = await connSettings.checkObjectExists('QSYS', 'QCPTOIMPF', '*DTAARA');
- if (QCPTOIMPF?.code === 0) {
+ if (QCPTOIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPTOIMPF exists on this system and may impact Code for IBM i functionality.`, {
detail: `For V5R3, the code for the command CPYTOIMPF had a major design change to increase functionality and performance. The QSYS/QCPTOIMPF data area lets developers keep the pre-V5R2 version of CPYTOIMPF. Code for IBM i cannot function correctly while this data area exists.`,
modal: true,
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- this.runCommand({
- command: `DLTOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- })
- .then((result) => {
- if (result?.code === 0) {
- vscode.window.showInformationMessage(`The data area QSYS/QCPTOIMPF has been deleted.`);
- } else {
- vscode.window.showInformationMessage(`Failed to delete the data area QSYS/QCPTOIMPF. Code for IBM i may not work as intended.`);
- }
- })
+ connSettings.deleteObject('QSYS', 'QCPTOIMPF', '*DTAARA').then((result) => {
+ if (result) {
+ vscode.window.showInformationMessage(`The data
+ area QSYS/QCPTOIMPF has been deleted.`);
+ }
+ else {
+ vscode.window.showInformationMessage(`Failed to
+ delete the data area QSYS/QCPTOIMPF. Code for IBM
+ i may not work as intended.`);
+ }
+ });
break;
case `Read more`:
vscode.env.openExternal(vscode.Uri.parse(`https://github.com/codefori/vscode-ibmi/issues/476#issuecomment-1018908018`));
@@ -513,23 +356,17 @@ export default class IBMi {
});
}
- const QCPFRMIMPF = await this.runCommand({
- command: `CHKOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- });
+ const QCPFRMIMPF = await connSettings.checkObjectExists('QSYS', 'QCPFRMIMPF', '*DTAARA');
- if (QCPFRMIMPF?.code === 0) {
+ if (QCPFRMIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPFRMIMPF exists on this system and may impact Code for IBM i functionality.`, {
modal: false,
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- this.runCommand({
- command: `DLTOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- })
+ connSettings.deleteObject('QSYS', 'QCPFRMIMPF', '*DTAARA')
.then((result) => {
- if (result?.code === 0) {
+ if (result) {
vscode.window.showInformationMessage(`The data area QSYS/QCPFRMIMPF has been deleted.`);
} else {
vscode.window.showInformationMessage(`Failed to delete the data area QSYS/QCPFRMIMPF. Code for IBM i may not work as intended.`);
@@ -553,8 +390,10 @@ export default class IBMi {
message: `Checking installed components on host IBM i.`
});
+ let remoteApps = new IBMiApps();
+
// We need to check if our remote programs are installed.
- remoteApps.push(
+ remoteApps.addRemoteApp(
{
path: `/QSYS.lib/${this.upperCaseName(this.config.tempLibrary)}.lib/`,
names: [`GETNEWLIBL.PGM`],
@@ -564,25 +403,15 @@ export default class IBMi {
//Next, we see what pase features are available (installed via yum)
//This may enable certain features in the future.
- for (const feature of remoteApps) {
+ for (const remoteApp of remoteApps.getRemoteApps()) {
try {
progress.report({
- message: `Checking installed components on host IBM i: ${feature.path}`
+ message: `Checking installed components on host IBM i: ${remoteApp.path}`
});
- const call = await this.sendCommand({ command: `ls -p ${feature.path}${feature.specific || ``}` });
- if (call.stdout) {
- const files = call.stdout.split(`\n`);
-
- if (feature.specific) {
- for (const name of feature.names)
- this.remoteFeatures[name] = files.find(file => file.includes(name));
- } else {
- for (const name of feature.names)
- if (files.includes(name))
- this.remoteFeatures[name] = feature.path + name;
- }
- }
+ await remoteApps.checkRemoteFeatures(remoteApp, this);
+ this.remoteFeatures = remoteApps.getRemoteFeatures();
+
} catch (e) {
console.log(e);
}
@@ -590,22 +419,6 @@ export default class IBMi {
}
if (this.sqlRunnerAvailable()) {
- //Temporary function to run SQL
-
- // TODO: stop using this runSQL function and this.runSql
- const runSQL = async (statement: string) => {
- const output = await this.sendCommand({
- command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`,
- stdin: statement
- });
-
- if (output.code === 0) {
- return Tools.db2Parse(output.stdout);
- }
- else {
- throw new Error(output.stdout);
- }
- };
// Check for ASP information?
if (quickConnect === true && cachedServerSettings?.aspInfo) {
@@ -617,19 +430,14 @@ export default class IBMi {
//This is mostly a nice to have. We grab the ASP info so user's do
//not have to provide the ASP in the settings.
- try {
- const resultSet = await runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
- resultSet.forEach(row => {
- if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
- this.aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
- }
- });
- } catch (e) {
- //Oh well
+
+ this.aspInfo = await connSettings.getASPInfo();
+ if (Object.keys(this.aspInfo).length === 0) {
progress.report({
message: `Failed to get ASP information.`
});
}
+
}
// Fetch conversion values?
@@ -646,18 +454,11 @@ export default class IBMi {
// Next, we're going to see if we can get the CCSID from the user or the system.
// Some things don't work without it!!!
try {
-
// we need to grab the system CCSID (QCCSID)
- const [systemCCSID] = await runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
- if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
- this.qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
- }
+ this.qccsid = await connSettings.getQCCSID();
// we grab the users default CCSID
- const [userInfo] = await runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`);
- if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
- this.jobCcsid = userInfo.CHARACTER_CODE_SET_ID;
- }
+ this.jobCcsid = await connSettings.getjobCCSID(this.currentUser);
// if the job ccsid is *SYSVAL, then assign it to sysval
if (this.jobCcsid === CCSID_SYSVAL) {
@@ -665,36 +466,14 @@ export default class IBMi {
}
// Let's also get the user's default CCSID
- try {
- const [activeJob] = await runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
- this.userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
- }
- catch (error) {
- const [defaultCCSID] = (await this.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
- .stdout
- .split("\n")
- .filter(line => line.includes("DFTCCSID"));
-
- const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
- if (defaultCCSCID && !isNaN(defaultCCSCID)) {
- this.userDefaultCCSID = defaultCCSCID;
- }
- }
+ this.userDefaultCCSID = await connSettings.getDefaultCCSID();
progress.report({
message: `Fetching local encoding values.`
});
- const [variants] = await runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
- + ` values ( cast( x'7B' as varchar(1) )`
- + ` , cast( x'7C' as varchar(1) )`
- + ` , cast( x'5B' as varchar(1) ) )`
- + `)`
- + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+ this.variantChars.local = await connSettings.getLocalEncodingValues();
- if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
- this.variantChars.local = variants.LOCAL;
- }
} catch (e) {
// Oh well!
console.log(e);
@@ -737,11 +516,10 @@ export default class IBMi {
vscode.window.showInformationMessage(`IBM recommends using bash as your default shell.`, `Set shell to bash`, `Read More`,).then(async choice => {
switch (choice) {
case `Set shell to bash`:
- const commandSetBashResult = await this.sendCommand({
- command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
- });
- if (!commandSetBashResult.stderr) {
+ const commandSetBashResult = await connSettings.setBash();
+
+ if (!commandSetBashResult) {
vscode.window.showInformationMessage(`Shell is now bash! Reconnect for change to take effect.`);
usesBash = true;
} else {
@@ -764,72 +542,32 @@ export default class IBMi {
});
if ((!quickConnect || !cachedServerSettings?.pathChecked)) {
- const currentPaths = (await this.sendCommand({ command: "echo $PATH" })).stdout.split(":");
- const bashrcFile = `${defaultHomeDir}/.bashrc`;
- let bashrcExists = (await this.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
- let reason;
- const requiredPaths = ["/QOpenSys/pkgs/bin", "/usr/bin", "/QOpenSys/usr/bin"]
- let missingPath;
- for (const requiredPath of requiredPaths) {
- if (!currentPaths.includes(requiredPath)) {
- reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
- missingPath = requiredPath
- break;
- }
- }
- // If reason is still undefined, then we know the user has all the required paths. Then we don't
- // need to check for their existence before checking the order of the required paths.
- if (!reason &&
- (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
- || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
- reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
- missingPath = "/QOpenSys/pkgs/bin"
- }
- if (reason && await vscode.window.showWarningMessage(`${missingPath} not found in $PATH`, {
+
+ const bashrcFile = `${homeResult.homeDir}/.bashrc`;
+
+ let bashrcExists = await connSettings.checkBashRCFile(bashrcFile);
+
+ let checkPathResult = await connSettings.checkPaths(["/QOpenSys/pkgs/bin", "/usr/bin", "/QOpenSys/usr/bin"]);
+
+ if (checkPathResult.reason && await vscode.window.showWarningMessage(`${checkPathResult.missingPath} not found in $PATH`, {
modal: true,
- detail: `${reason}, so Code for IBM i may not function correctly. Would you like to ${bashrcExists ? "update" : "create"} ${bashrcFile} to fix this now?`,
+ detail: `${checkPathResult.reason}, so Code for IBM i may not function correctly. Would you like to ${bashrcExists ? "update" : "create"} ${bashrcFile} to fix this now?`,
}, `Yes`)) {
delayedOperations.push(async () => {
this.appendOutput(`${bashrcExists ? "update" : "create"} ${bashrcFile}`);
if (!bashrcExists) {
- // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
- // all the required paths, but we don't overwrite the priority of other items on their path.
- const createBashrc = await this.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${connectionObject.username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
- if (createBashrc.code !== 0) {
- vscode.window.showWarningMessage(`Error creating ${bashrcFile}):\n${createBashrc.stderr}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
+ //Create bashrc File
+ let createBashResult = await connSettings.createBashrcFile(bashrcFile,connectionObject.username);
+ //Error creating bashrc File
+ if (!createBashResult.createBash) {
+ vscode.window.showWarningMessage(`Error creating ${bashrcFile}):\n${createBashResult.createBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
}
}
else {
- try {
- const content = this.content;
- if (content) {
- const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
- let replaced = false;
- bashrcContent.forEach((line, index) => {
- if (!replaced) {
- const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
- if (pathRegex) {
- bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
- .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
- .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
- replaced = true;
- }
- }
- });
-
- if (!replaced) {
- bashrcContent.push(
- "",
- "# Generated by Code for IBM i",
- "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
- );
- }
-
- await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
- }
- }
- catch (error) {
- vscode.window.showWarningMessage(`Error modifying PATH in ${bashrcFile}):\n${error}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
+ //Update bashRC file
+ let updateBashResult = await connSettings.updateBashrcFile(bashrcFile);
+ if(!updateBashResult.updateBash) {
+ vscode.window.showWarningMessage(`Error modifying PATH in ${bashrcFile}):\n${updateBashResult.updateBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
}
}
});
@@ -850,7 +588,7 @@ export default class IBMi {
}
}
- if (defaultHomeDir) {
+ if (homeResult.homeDir) {
if (!tempLibrarySet) {
vscode.window.showWarningMessage(`Code for IBM i will not function correctly until the temporary library has been corrected in the settings.`, `Open Settings`)
.then(result => {
@@ -873,34 +611,14 @@ export default class IBMi {
progress.report({
message: `Validate configured library list`
});
- let validLibs: string[] = [];
- let badLibs: string[] = [];
-
- const result = await this.sendQsh({
- command: [
- `liblist -d ` + this.defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
- ...this.config.libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
- ].join(`; `)
- });
- if (result.stderr) {
- const lines = result.stderr.split(`\n`);
-
- lines.forEach(line => {
- const badLib = this.config?.libraryList.find(lib => line.includes(`ibrary ${lib} `));
-
- // If there is an error about the library, store it
- if (badLib) badLibs.push(badLib);
- });
- }
-
- if (result && badLibs.length > 0) {
- validLibs = this.config.libraryList.filter(lib => !badLibs.includes(lib));
- const chosen = await vscode.window.showWarningMessage(`The following ${badLibs.length > 1 ? `libraries` : `library`} does not exist: ${badLibs.join(`,`)}. Remove ${badLibs.length > 1 ? `them` : `it`} from the library list?`, `Yes`, `No`);
+ let libraryListResult = await connSettings.validateLibraryList(this.defaultUserLibraries,this.config.libraryList);
+ if(libraryListResult.badLibs.length > 0) {
+ const chosen = await vscode.window.showWarningMessage(`The following ${libraryListResult.badLibs.length > 1 ? `libraries` : `library`} does not exist: ${libraryListResult.badLibs.join(`,`)}. Remove ${libraryListResult.badLibs.length > 1 ? `them` : `it`} from the library list?`, `Yes`, `No`);
if (chosen === `Yes`) {
- this.config!.libraryList = validLibs;
+ this.config!.libraryList = libraryListResult.validLibs;
} else {
- vscode.window.showWarningMessage(`The following libraries does not exist: ${badLibs.join(`,`)}.`);
+ vscode.window.showWarningMessage(`The following libraries does not exist: ${libraryListResult.badLibs.join(`,`)}.`);
}
}
}
@@ -1365,6 +1083,8 @@ export default class IBMi {
* @param statements
* @returns a Result set
*/
+
+ // TODO: stop using this.runSql
async runSQL(statements: string): Promise {
const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.remoteFeatures;
diff --git a/src/api/IBMiApps.ts b/src/api/IBMiApps.ts
new file mode 100644
index 000000000..abfc48974
--- /dev/null
+++ b/src/api/IBMiApps.ts
@@ -0,0 +1,87 @@
+import IBMi from "./IBMi";
+import { RemoteApp, RemoteApps, RemoteFeatures } from "../typings";
+
+export default class IBMiApps {
+
+ private remoteApps: RemoteApps;
+ private remoteFeatures: RemoteFeatures;
+
+ constructor() {
+ this.remoteApps = [
+ {
+ path: `/usr/bin/`,
+ names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
+ },
+ {
+ path: `/QOpenSys/pkgs/bin/`,
+ names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
+ },
+ {
+ path: `/QSYS.LIB/`,
+ // In the future, we may use a generic specific.
+ // Right now we only need one program
+ // specific: `*.PGM`,
+ specific: `QZDFMDB2.PGM`,
+ names: [`QZDFMDB2.PGM`]
+ },
+ {
+ path: `/QIBM/ProdData/IBMiDebugService/bin/`,
+ specific: `startDebugService.sh`,
+ names: [`startDebugService.sh`]
+ }
+ ];
+
+ this.remoteFeatures = {};
+ this.setRemoteFeatures();
+
+ }
+
+ addRemoteApp(remoteApp: RemoteApp) {
+
+ //Add remote App
+ this.remoteApps.push(remoteApp);
+
+ //Add possible features to list
+ for(const name of remoteApp.names) {
+ this.remoteFeatures[name] = undefined;
+ }
+
+ }
+
+ getRemoteApps(): RemoteApps {
+ return this.remoteApps;
+ }
+
+ setRemoteFeatures() {
+
+ for (const feature of this.remoteApps) {
+ for(const name of feature.names) {
+ this.remoteFeatures[name] = undefined;
+ }
+ }
+
+ }
+
+ getRemoteFeatures(): RemoteFeatures {
+ return this.remoteFeatures;
+ }
+
+ async checkRemoteFeatures(remoteApp: RemoteApp, connection: IBMi) {
+
+ const call = await connection.sendCommand({ command: `ls -p ${remoteApp.path}${remoteApp.specific || ``}` });
+ if (call.stdout) {
+ const files = call.stdout.split(`\n`);
+
+ if (remoteApp.specific) {
+ for (const name of remoteApp.names)
+ this.remoteFeatures[name] = files.find(file => file.includes(name));
+ } else {
+ for (const name of remoteApp.names)
+ if (files.includes(name))
+ this.remoteFeatures[name] = remoteApp.path + name;
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/api/IBMiSettings.ts b/src/api/IBMiSettings.ts
new file mode 100644
index 000000000..ad5d07869
--- /dev/null
+++ b/src/api/IBMiSettings.ts
@@ -0,0 +1,515 @@
+import IBMi from "./IBMi";
+import { Tools } from "./Tools";
+import path from 'path';
+import { aspInfo } from "../typings";
+
+const CCSID_SYSVAL = -2;
+
+export default class IBMiSettings {
+
+ constructor(private connection: IBMi) {
+
+ }
+
+ async checkShellOutput(): Promise {
+
+ const checkShellText = `This should be the only text!`;
+ const checkShellResult = await this.connection.sendCommand({
+ command: `echo "${checkShellText}"`,
+ directory: `.`
+ });
+
+ return Promise.resolve(checkShellResult.stdout.split(`\n`)[0] == checkShellText);
+
+ }
+
+ async getHomeDirectory(): Promise<{ homeExists: boolean, homeDir: string, homeMsg: string }> {
+
+ let homeDir;
+ let homeMsg = '';
+ let homeExists;
+
+ const homeResult = await this.connection.sendCommand({
+ command: `echo $HOME && cd && test -w $HOME`,
+ directory: `.`
+ });
+
+ // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
+ // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
+ // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
+ // if we iterate on this code again in the future)
+ // - stdout contains the name of the home directory (even if it does not exist)
+ // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
+ // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
+
+ homeExists = homeResult.code == 0;
+ homeDir = homeResult.stdout.trim();
+
+ if (!homeExists) {
+ // Let's try to provide more valuable information to the user about why their home directory
+ // is bad and maybe even provide the opportunity to create the home directory
+
+ // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
+ homeExists = (0 === (await this.connection.sendCommand({ command: `test -e ${homeDir}` })).code);
+ if (homeExists) {
+ // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
+ // But, remember, but we only got here if 'cd $HOME' failed.
+ // Let's try to figure out why....
+ if (0 !== (await this.connection.sendCommand({ command: `test -d ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -w ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`;
+
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -x ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else {
+ // not sure, but get your sys admin involved
+ homeMsg = `Your home directory (${homeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ }
+ else {
+ homeMsg = `Your home directory (${homeDir}) does not exist, so Code for IBM i may not function correctly.`;
+ }
+ }
+
+ return Promise.resolve({ homeExists, homeDir, homeMsg });
+
+ }
+
+ async createHomeDirectory(homeDir: string, username: string): Promise<{ homeCreated: boolean, homeMsg: string }> {
+
+ let homeCreated = false;
+ let homeMsg = '';
+
+ const homeCmd = `mkdir -p ${homeDir} && chown ${username.toLowerCase()} ${homeDir} && chmod 0755 ${homeDir}`;
+
+ let mkHomeResult = await this.connection.sendCommand({ command: homeCmd, directory: `.` });
+
+ if (0 === mkHomeResult.code) {
+ homeCreated = true;
+ } else {
+ let mkHomeErrs = mkHomeResult.stderr;
+ // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
+ homeMsg = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
+ }
+
+ return Promise.resolve({ homeCreated, homeMsg });
+
+ }
+
+ async getLibraryList(): Promise<{ libStatus: boolean, currentLibrary: string, defaultUserLibraries: string[] }> {
+
+
+
+ //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
+ //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
+
+ let currentLibrary = `QGPL`;
+ let defaultUserLibraries = [];
+ let libStatus = false;
+
+ const liblResult = await this.connection.sendQsh({
+ command: `liblist`
+ });
+
+ if (liblResult.code === 0) {
+ libStatus = true;
+ const libraryListString = liblResult.stdout;
+ if (libraryListString !== ``) {
+ const libraryList = libraryListString.split(`\n`);
+
+ let lib, type;
+ for (const line of libraryList) {
+ lib = line.substring(0, 10).trim();
+ type = line.substring(12);
+
+ switch (type) {
+ case `USR`:
+ defaultUserLibraries.push(lib);
+ break;
+
+ case `CUR`:
+ currentLibrary = lib;
+ break;
+ }
+ }
+ }
+ }
+
+ return Promise.resolve({ libStatus, currentLibrary, defaultUserLibraries });
+
+ }
+
+ async setTempLibrary(tempLibrary: string): Promise {
+
+ let tempLibrarySet = false;
+
+ //Check the temp lib (where temp outfile data lives) exists
+ const createdTempLib = await this.connection.runCommand({
+ command: `CRTLIB LIB(${tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
+ noLibList: true
+ });
+
+ if (createdTempLib.code === 0) {
+ tempLibrarySet = true;
+ }
+ else {
+ const messages = Tools.parseMessages(createdTempLib.stderr);
+ if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
+ tempLibrarySet = true;
+ }
+ else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
+ const tempLibExists = await this.connection.runCommand({
+ command: `CHKOBJ OBJ(QSYS/${tempLibrary}) OBJTYPE(*LIB)`,
+ noLibList: true
+ });
+
+ if (tempLibExists.code === 0) {
+ //We're all good if no errors
+ tempLibrarySet = true;
+ }
+ else {
+ tempLibrarySet = false;
+ }
+
+ }
+ }
+
+ return Promise.resolve(tempLibrarySet);
+
+ }
+
+ async setTempDirectory(tempDir: string): Promise {
+
+ let tempDirSet = false;
+
+ // Check if the temp directory exists
+ let result = await this.connection.sendCommand({
+ command: `[ -d "${tempDir}" ]`
+ });
+
+ if (result.code === 0) {
+ // Directory exists
+ tempDirSet = true;
+ } else {
+ // Directory does not exist, try to create it
+ let result = await this.connection.sendCommand({
+ command: `mkdir -p ${tempDir}`
+ });
+ if (result.code === 0) {
+ // Directory created
+ tempDirSet = true;
+ } else {
+ // Directory not created
+ }
+ }
+
+ return Promise.resolve(tempDirSet);
+
+ }
+
+ async clearTempLibrary(tempLibrary: string): Promise {
+
+ let clearMsg = '';
+
+ this.connection.runCommand({
+ command: `DLTOBJ OBJ(${tempLibrary}/O_*) OBJTYPE(*FILE)`,
+ noLibList: true,
+ })
+ .then(result => {
+ // All good!
+ if (result && result.stderr) {
+ const messages = Tools.parseMessages(result.stderr);
+ if (!messages.findId(`CPF2125`)) {
+ clearMsg = `Temporary data not cleared from ${tempLibrary}.`;
+ }
+ }
+ });
+
+ return Promise.resolve(clearMsg);
+
+ }
+
+ async clearTempDirectory(tempDir: string): Promise {
+
+ let clearMsg = '';
+
+ this.connection.sendCommand({
+ command: `rm -rf ${path.posix.join(tempDir, `vscodetemp*`)}`
+ })
+ .then(result => {
+ // All good!
+ })
+ .catch(e => {
+ // CPF2125: No objects deleted.
+ clearMsg = `Temporary data not cleared from ${tempDir}.`;
+ });
+
+ return Promise.resolve(clearMsg);
+
+ }
+
+ async checkObjectExists(library: string, object: string, type: string): Promise {
+
+ const objResult = await this.connection.runCommand({
+ command: `CHKOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
+ noLibList: true
+ });
+
+ return Promise.resolve(objResult.code === 0);
+
+ }
+
+ async deleteObject(library: string, object: string, type: string): Promise {
+
+ const deleteResult = await this.connection.runCommand({
+ command: `DLTOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
+ noLibList: true
+ });
+
+ return Promise.resolve(deleteResult.code === 0);
+
+ }
+
+ async getASPInfo(): Promise {
+
+ let aspInfo: aspInfo = {};
+
+ try {
+ const resultSet = await this.connection.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
+ if (resultSet.length) {
+ resultSet.forEach(row => {
+ if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
+ aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
+ }
+ });
+ }
+ } catch (e) {
+ //Oh well
+ aspInfo = {};
+ }
+
+ return Promise.resolve(aspInfo);
+
+ }
+
+ async getQCCSID(): Promise {
+
+ let qccsid = 0;
+
+ const [systemCCSID] = await this.connection.runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
+ if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
+ qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
+ }
+
+ return Promise.resolve(qccsid);
+
+ }
+
+ async getjobCCSID(userName: string): Promise {
+
+ let jobCCSID = CCSID_SYSVAL;
+
+ const [userInfo] = await this.connection.runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${userName}') ) )`);
+ if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
+ jobCCSID = userInfo.CHARACTER_CODE_SET_ID;
+ }
+
+ return Promise.resolve(jobCCSID);
+
+ }
+
+ async getDefaultCCSID(): Promise {
+
+ let userDefaultCCSID = 0;
+
+ try {
+ const [activeJob] = await this.connection.runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
+ userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
+ }
+ catch (error) {
+ const [defaultCCSID] = (await this.connection.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
+ .stdout
+ .split("\n")
+ .filter(line => line.includes("DFTCCSID"));
+
+ const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
+ if (defaultCCSCID && !isNaN(defaultCCSCID)) {
+ userDefaultCCSID = defaultCCSCID;
+ }
+ }
+
+ return Promise.resolve(userDefaultCCSID);
+
+ }
+
+ async getLocalEncodingValues(): Promise {
+
+ let localEncoding = '';
+
+ const [variants] = await this.connection.runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
+ + ` values ( cast( x'7B' as varchar(1) )`
+ + ` , cast( x'7C' as varchar(1) )`
+ + ` , cast( x'5B' as varchar(1) ) )`
+ + `)`
+ + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+
+ if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
+ localEncoding = variants.LOCAL;
+ }
+
+ return Promise.resolve(localEncoding);
+
+ }
+
+ async setBash(): Promise {
+
+ let bashset = false;
+
+ const commandSetBashResult = await this.connection.sendCommand({
+ command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
+ });
+
+ if (!commandSetBashResult.stderr) bashset = true;
+
+ return Promise.resolve(bashset);
+
+ }
+
+ async getEnvironmentVariable(envVar: string): Promise {
+ return (await this.connection.sendCommand({ command: `echo ${envVar}` })).stdout.split(":");
+ }
+
+ async checkPaths(requiredPaths: string[]): Promise<{ reason: string, missingPath: string }> {
+
+ const currentPaths = await this.getEnvironmentVariable('$PATH');
+
+ let reason = '';
+ let missingPath = '';
+
+ for (const requiredPath of requiredPaths) {
+ if (!currentPaths.includes(requiredPath)) {
+ reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
+ missingPath = requiredPath
+ break;
+ }
+ }
+ // If reason is still undefined, then we know the user has all the required paths. Then we don't
+ // need to check for their existence before checking the order of the required paths.
+ if (!reason) {
+ for (let x = 1; x <= requiredPaths.length; x++) {
+ if (currentPaths.indexOf(requiredPaths[0]) > currentPaths.indexOf(requiredPaths[x])) {
+ reason = `${requiredPaths[0]} is not in the right position in your $PATH shell environment variable`;
+ missingPath = requiredPaths[0];
+ break;
+ }
+ }
+ }
+
+ return Promise.resolve({ reason: reason, missingPath: missingPath });
+
+ }
+
+ async checkBashRCFile(bashrcFile: string): Promise {
+
+ let bashrcExists = false;
+
+ bashrcExists = (await this.connection.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
+
+ return Promise.resolve(bashrcExists);
+ }
+
+ async createBashrcFile(bashrcFile: string, username: string): Promise<{ createBash: boolean, createBashMsg: string }> {
+
+ let createBash = true;
+ let createBashMsg = '';
+
+ // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
+ // all the required paths, but we don't overwrite the priority of other items on their path.
+ const createBashrc = await this.connection.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
+
+ if (createBashrc.code !== 0) {
+ createBash = false;
+ createBashMsg = createBashrc.stderr;
+ }
+
+ return Promise.resolve({ createBash, createBashMsg });
+
+ }
+
+ async updateBashrcFile(bashrcFile: string): Promise<{ updateBash: boolean, updateBashMsg: string }> {
+
+ let updateBash = true;
+ let updateBashMsg = '';
+
+ try {
+ const content = this.connection.content;
+ if (content) {
+ const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
+ let replaced = false;
+ bashrcContent.forEach((line, index) => {
+ if (!replaced) {
+ const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
+ if (pathRegex) {
+ bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
+ .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
+ .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
+ replaced = true;
+ }
+ }
+ });
+
+ if (!replaced) {
+ bashrcContent.push(
+ "",
+ "# Generated by Code for IBM i",
+ "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
+ );
+ }
+
+ await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
+ }
+ }
+ catch (error) {
+ updateBash = false;
+ updateBashMsg = error;
+ }
+
+ return Promise.resolve({ updateBash, updateBashMsg });
+ }
+
+ async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{validLibs: string[], badLibs: string[]}> {
+
+ let validLibs: string[] = [];
+ let badLibs: string[] = [];
+
+ const result = await this.connection.sendQsh({
+ command: [
+ `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
+ ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
+ ].join(`; `)
+ });
+
+ if (result.stderr) {
+ const lines = result.stderr.split(`\n`);
+
+ lines.forEach(line => {
+ const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
+
+ // If there is an error about the library, store it
+ if (badLib) badLibs.push(badLib);
+ });
+ }
+
+ if (result && badLibs.length > 0) {
+ validLibs = libraryList.filter(lib => !badLibs.includes(lib));
+ }
+
+ return Promise.resolve({validLibs,badLibs});
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/typings.ts b/src/typings.ts
index cceef76f0..531e73849 100644
--- a/src/typings.ts
+++ b/src/typings.ts
@@ -238,4 +238,23 @@ export type SearchHit = {
export type SearchHitLine = {
number: number
content: string
-}
\ No newline at end of file
+}
+
+export interface MemberParts extends IBMiMember {
+ basename: string
+}
+
+
+export type RemoteApp = {
+ path: string
+ specific ?: string
+ names: string[]
+}
+
+export type RemoteApps = RemoteApp[];
+
+export type RemoteFeature = string | undefined;
+
+export type RemoteFeatures = { [name: string]: RemoteFeature };
+
+export type aspInfo = { [id: number]: string };
From 9564ffcaf664e1dced3bd5b5867545c95089ae92 Mon Sep 17 00:00:00 2001
From: krethan
Date: Tue, 1 Oct 2024 23:38:26 -0600
Subject: [PATCH 10/28] Fixed few issues introduced with IBMiSettings class.
---
src/api/IBMi.ts | 30 +++++++++++++++-----------
src/api/IBMiSettings.ts | 48 ++++++++++++++++++++---------------------
2 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 5e929a48b..4ee77eaa8 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -243,7 +243,7 @@ export default class IBMi {
let libraryListResult = await connSettings.getLibraryList();
if (libraryListResult.libStatus) {
-
+
this.defaultUserLibraries = libraryListResult.defaultUserLibraries;
//If this is the first time the config is made, then these arrays will be empty
@@ -409,12 +409,14 @@ export default class IBMi {
});
await remoteApps.checkRemoteFeatures(remoteApp, this);
- this.remoteFeatures = remoteApps.getRemoteFeatures();
} catch (e) {
console.log(e);
}
}
+
+ this.remoteFeatures = remoteApps.getRemoteFeatures();
+
}
if (this.sqlRunnerAvailable()) {
@@ -429,9 +431,11 @@ export default class IBMi {
//This is mostly a nice to have. We grab the ASP info so user's do
//not have to provide the ASP in the settings.
-
- this.aspInfo = await connSettings.getASPInfo();
- if (Object.keys(this.aspInfo).length === 0) {
+ try {
+ this.aspInfo = await connSettings.getASPInfo();
+ }
+ catch (e) {
+ //Oh well
progress.report({
message: `Failed to get ASP information.`
});
@@ -541,13 +545,13 @@ export default class IBMi {
});
if ((!quickConnect || !cachedServerSettings?.pathChecked)) {
-
+
const bashrcFile = `${homeResult.homeDir}/.bashrc`;
-
+
let bashrcExists = await connSettings.checkBashRCFile(bashrcFile);
-
+
let checkPathResult = await connSettings.checkPaths(["/QOpenSys/pkgs/bin", "/usr/bin", "/QOpenSys/usr/bin"]);
-
+
if (checkPathResult.reason && await vscode.window.showWarningMessage(`${checkPathResult.missingPath} not found in $PATH`, {
modal: true,
detail: `${checkPathResult.reason}, so Code for IBM i may not function correctly. Would you like to ${bashrcExists ? "update" : "create"} ${bashrcFile} to fix this now?`,
@@ -556,7 +560,7 @@ export default class IBMi {
this.appendOutput(`${bashrcExists ? "update" : "create"} ${bashrcFile}`);
if (!bashrcExists) {
//Create bashrc File
- let createBashResult = await connSettings.createBashrcFile(bashrcFile,connectionObject.username);
+ let createBashResult = await connSettings.createBashrcFile(bashrcFile, connectionObject.username);
//Error creating bashrc File
if (!createBashResult.createBash) {
vscode.window.showWarningMessage(`Error creating ${bashrcFile}):\n${createBashResult.createBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
@@ -565,7 +569,7 @@ export default class IBMi {
else {
//Update bashRC file
let updateBashResult = await connSettings.updateBashrcFile(bashrcFile);
- if(!updateBashResult.updateBash) {
+ if (!updateBashResult.updateBash) {
vscode.window.showWarningMessage(`Error modifying PATH in ${bashrcFile}):\n${updateBashResult.updateBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
}
}
@@ -611,8 +615,8 @@ export default class IBMi {
message: `Validate configured library list`
});
- let libraryListResult = await connSettings.validateLibraryList(this.defaultUserLibraries,this.config.libraryList);
- if(libraryListResult.badLibs.length > 0) {
+ let libraryListResult = await connSettings.validateLibraryList(this.defaultUserLibraries, this.config.libraryList);
+ if (libraryListResult.badLibs.length > 0) {
const chosen = await vscode.window.showWarningMessage(`The following ${libraryListResult.badLibs.length > 1 ? `libraries` : `library`} does not exist: ${libraryListResult.badLibs.join(`,`)}. Remove ${libraryListResult.badLibs.length > 1 ? `them` : `it`} from the library list?`, `Yes`, `No`);
if (chosen === `Yes`) {
this.config!.libraryList = libraryListResult.validLibs;
diff --git a/src/api/IBMiSettings.ts b/src/api/IBMiSettings.ts
index ad5d07869..c3eb961a9 100644
--- a/src/api/IBMiSettings.ts
+++ b/src/api/IBMiSettings.ts
@@ -289,7 +289,7 @@ export default class IBMiSettings {
}
} catch (e) {
//Oh well
- aspInfo = {};
+ return Promise.reject(e);
}
return Promise.resolve(aspInfo);
@@ -399,14 +399,12 @@ export default class IBMiSettings {
}
// If reason is still undefined, then we know the user has all the required paths. Then we don't
// need to check for their existence before checking the order of the required paths.
- if (!reason) {
- for (let x = 1; x <= requiredPaths.length; x++) {
- if (currentPaths.indexOf(requiredPaths[0]) > currentPaths.indexOf(requiredPaths[x])) {
- reason = `${requiredPaths[0]} is not in the right position in your $PATH shell environment variable`;
- missingPath = requiredPaths[0];
- break;
- }
- }
+
+ if (!reason &&
+ (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
+ || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
+ reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
+ missingPath = "/QOpenSys/pkgs/bin"
}
return Promise.resolve({ reason: reason, missingPath: missingPath });
@@ -481,34 +479,34 @@ export default class IBMiSettings {
return Promise.resolve({ updateBash, updateBashMsg });
}
- async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{validLibs: string[], badLibs: string[]}> {
-
- let validLibs: string[] = [];
- let badLibs: string[] = [];
+ async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{ validLibs: string[], badLibs: string[] }> {
+
+ let validLibs: string[] = [];
+ let badLibs: string[] = [];
- const result = await this.connection.sendQsh({
+ const result = await this.connection.sendQsh({
command: [
- `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
- ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
+ `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
+ ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
].join(`; `)
- });
+ });
- if (result.stderr) {
+ if (result.stderr) {
const lines = result.stderr.split(`\n`);
lines.forEach(line => {
- const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
+ const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
- // If there is an error about the library, store it
- if (badLib) badLibs.push(badLib);
+ // If there is an error about the library, store it
+ if (badLib) badLibs.push(badLib);
});
- }
+ }
- if (result && badLibs.length > 0) {
+ if (result && badLibs.length > 0) {
validLibs = libraryList.filter(lib => !badLibs.includes(lib));
- }
+ }
- return Promise.resolve({validLibs,badLibs});
+ return Promise.resolve({ validLibs, badLibs });
}
From 9f6a3556146aa07eb5a65ae80e8e5233503ce450 Mon Sep 17 00:00:00 2001
From: krethan
Date: Tue, 1 Oct 2024 23:41:30 -0600
Subject: [PATCH 11/28] Removed unneccessary comments
---
src/api/IBMi.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 4ee77eaa8..13ba090b2 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -309,12 +309,10 @@ export default class IBMi {
}
- //TO DO: why is this required????
const commandShellResult = await this.sendCommand({
command: `echo $SHELL`
});
- //TO DO: why is this required????
if (commandShellResult.code === 0) {
this.shell = commandShellResult.stdout.trim();
}
From 226d0ce05f6201b88c7cd64f74d4b0cb2cf9c868 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Thu, 3 Oct 2024 18:06:34 +0200
Subject: [PATCH 12/28] Fixed wrong import
Signed-off-by: Seb Julliand
---
src/api/IBMi.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 838bce179..06ec6b5d8 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -4,7 +4,7 @@ import * as node_ssh from "node-ssh";
import os from "os";
import path, { parse as parsePath } from 'path';
import * as vscode from "vscode";
-import { IBMiComponent, IBMiComponentType } from "../components/componen
+import { IBMiComponent, IBMiComponentType } from "../components/component";
import { CopyToImport } from "../components/copyToImport";
import { ComponentManager } from "../components/manager";
import { instance } from "../instantiate";
@@ -1353,7 +1353,7 @@ export default class IBMi {
}
}
- getComponent(type: IBMiComponentType, ignoreState?:boolean): T | undefined {
+ getComponent(type: IBMiComponentType, ignoreState?: boolean): T | undefined {
return this.componentManager.get(type, ignoreState);
}
From 34fda10907b7588ee55ee6ca99730a01c77debbd Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Fri, 4 Oct 2024 17:22:45 +0200
Subject: [PATCH 13/28] Always show "Download client certificate" button
Especially when there is a problem...
Signed-off-by: Seb Julliand
---
src/webviews/settings/index.ts | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts
index 7694d390d..4ec8d6ade 100644
--- a/src/webviews/settings/index.ts
+++ b/src/webviews/settings/index.ts
@@ -199,10 +199,8 @@ export class SettingsUI {
localCertificateIssue = `${String(error)}. Debugging will not function correctly.`;
}
debuggerTab.addParagraph(`${localCertificateIssue || "Client certificate for service has been imported and matches remote certificate."}`)
- .addParagraph(`To debug on IBM i, Visual Studio Code needs to load a client certificate to connect to the Debug Service. Each server has a unique certificate. This client certificate should exist at ${certificates.getLocalCertPath(connection)}
`);
- if (!localCertificateIssue) {
- debuggerTab.addButtons({ id: `import`, label: `Download client certificate` })
- }
+ .addParagraph(`To debug on IBM i, Visual Studio Code needs to load a client certificate to connect to the Debug Service. Each server has a unique certificate. This client certificate should exist at ${certificates.getLocalCertPath(connection)}
`)
+ .addButtons({ id: `import`, label: `Download client certificate` });
}
else {
debuggerTab.addParagraph(`The service certificate doesn't exist or is incomplete; it must be generated before the debug service can be started.`)
From f79e370e08962acc2a8032ca719257dbbced4144 Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Fri, 4 Oct 2024 13:35:44 -0400
Subject: [PATCH 14/28] Use type for component state instead of enum
Signed-off-by: worksofliam
---
src/components/component.ts | 14 ++++----------
src/components/copyToImport.ts | 4 ++--
src/components/getMemberInfo.ts | 10 +++++-----
src/components/getNewLibl.ts | 12 ++++++------
src/components/manager.ts | 2 +-
5 files changed, 18 insertions(+), 24 deletions(-)
diff --git a/src/components/component.ts b/src/components/component.ts
index 0612c6f11..74e2d1e47 100644
--- a/src/components/component.ts
+++ b/src/components/component.ts
@@ -1,12 +1,6 @@
import IBMi from "../api/IBMi";
-export const enum ComponentState {
- NotChecked = `Not checked`,
- NotInstalled = `Not installed`,
- Installed = `Installed`,
- NeedUpdate = `Need update`,
- Error = `Error`,
-}
+export type ComponentState = `NotChecked` | `NotInstalled` | `Installed` | `NeedsUpdate` | `Error`;
export type ComponentIdentification = {
name: string
@@ -39,7 +33,7 @@ export type IBMiComponentType = new (c: IBMi) => T;
*
*/
export abstract class IBMiComponent {
- private state = ComponentState.NotChecked;
+ private state: ComponentState = `NotChecked`;
constructor(protected readonly connection: IBMi) {
@@ -52,14 +46,14 @@ export abstract class IBMiComponent {
async check() {
try {
this.state = await this.getRemoteState();
- if (this.state !== ComponentState.Installed) {
+ if (this.state !== `Installed`) {
this.state = await this.update();
}
}
catch (error) {
console.log(`Error occurred while checking component ${this.toString()}`);
console.log(error);
- this.state = ComponentState.Error;
+ this.state = `Error`;
}
return this;
diff --git a/src/components/copyToImport.ts b/src/components/copyToImport.ts
index 7232acc18..854b73062 100644
--- a/src/components/copyToImport.ts
+++ b/src/components/copyToImport.ts
@@ -17,8 +17,8 @@ export class CopyToImport extends IBMiComponent {
return { name: 'CopyToImport', version: 1 };
}
- protected getRemoteState() {
- return ComponentState.Installed;
+ protected getRemoteState(): ComponentState {
+ return `Installed`;
}
protected update(): ComponentState | Promise {
diff --git a/src/components/getMemberInfo.ts b/src/components/getMemberInfo.ts
index 2f86720e2..0caa3bcb0 100644
--- a/src/components/getMemberInfo.ts
+++ b/src/components/getMemberInfo.ts
@@ -22,13 +22,13 @@ export class GetMemberInfo extends IBMiComponent {
}
}
if (this.installedVersion < this.currentVersion) {
- return ComponentState.NeedUpdate;
+ return `NeedsUpdate`;
}
- return ComponentState.Installed;
+ return `Installed`;
}
- protected async update() {
+ protected async update(): Promise {
const config = this.connection.config!;
return this.connection.withTempDirectory(async tempDir => {
const tempSourcePath = posix.join(tempDir, `getMemberInfo.sql`);
@@ -40,9 +40,9 @@ export class GetMemberInfo extends IBMiComponent {
});
if (result.code) {
- return ComponentState.Error;
+ return `Error`;
} else {
- return ComponentState.Installed;
+ return `Installed`;
}
});
}
diff --git a/src/components/getNewLibl.ts b/src/components/getNewLibl.ts
index 64014eef7..7d0b98860 100644
--- a/src/components/getNewLibl.ts
+++ b/src/components/getNewLibl.ts
@@ -7,14 +7,14 @@ export class GetNewLibl extends IBMiComponent {
return { name: 'GetNewLibl', version: 1 };
}
- protected async getRemoteState() {
- return this.connection.remoteFeatures[`GETNEWLIBL.PGM`] ? ComponentState.Installed : ComponentState.NotInstalled;
+ protected async getRemoteState(): Promise {
+ return this.connection.remoteFeatures[`GETNEWLIBL.PGM`] ? `Installed` : `NotInstalled`;
}
- protected update() {
+ protected update(): Promise {
const config = this.connection.config!
const content = instance.getContent();
- return this.connection.withTempDirectory(async tempDir => {
+ return this.connection.withTempDirectory(async (tempDir): Promise => {
const tempSourcePath = posix.join(tempDir, `getnewlibl.sql`);
await content!.writeStreamfileRaw(tempSourcePath, getSource(config.tempLibrary));
@@ -25,9 +25,9 @@ export class GetNewLibl extends IBMiComponent {
});
if (!result.code) {
- return ComponentState.Installed;
+ return `Installed`;
} else {
- return ComponentState.Error;
+ return `Error`;
}
});
}
diff --git a/src/components/manager.ts b/src/components/manager.ts
index ec0c19152..60b8d9e0e 100644
--- a/src/components/manager.ts
+++ b/src/components/manager.ts
@@ -39,7 +39,7 @@ export class ComponentManager {
get(type: IBMiComponentType, ignoreState?: boolean): T | undefined {
const component = this.registered.get(type);
- if (component && (ignoreState || component.getState() === ComponentState.Installed)) {
+ if (component && (ignoreState || component.getState() === `Installed`)) {
return component as T;
}
}
From 41609bec727a02eea2b38132e61de0d7c87a24cd Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Mon, 7 Oct 2024 21:57:51 +0200
Subject: [PATCH 15/28] Discover IBM i Java installation
Signed-off-by: Seb Julliand
---
src/api/IBMi.ts | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 06ec6b5d8..789074727 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -113,6 +113,10 @@ export default class IBMi {
tar: undefined,
ls: undefined,
find: undefined,
+ jdk80: undefined,
+ jdk11: undefined,
+ jdk17: undefined,
+ openjdk11: undefined
};
this.variantChars = {
@@ -587,6 +591,16 @@ export default class IBMi {
console.log(e);
}
}
+
+ //Specific Java installations check
+ progress.report({
+ message: `Checking installed components on host IBM i: Java`
+ });
+ const javaCheck = async (root: string) => await this.content.testStreamFile(`${root}/bin/java`, 'x') ? `${root}` : undefined;
+ this.remoteFeatures.jdk80 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`);
+ this.remoteFeatures.jdk11 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit`);
+ this.remoteFeatures.openjdk11 = await javaCheck(`/QOpensys/pkgs/lib/jvm/openjdk-11`);
+ this.remoteFeatures.jdk17 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk17/64bit`);
}
if (this.sqlRunnerAvailable()) {
From d23ff7d0e269ffb46e5f535f6dd6c755def13a09 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Mon, 7 Oct 2024 22:10:36 +0200
Subject: [PATCH 16/28] Use IBMi remote Java feature for debug service
Signed-off-by: Seb Julliand
---
src/api/debug/certificates.ts | 4 ++--
src/api/debug/config.ts | 12 ++++++++----
src/locale/ids/da.json | 1 +
src/locale/ids/de.json | 1 +
src/locale/ids/en.json | 1 +
src/locale/ids/fr.json | 1 +
src/locale/ids/no.json | 1 +
src/locale/ids/pl.json | 1 +
src/testing/debug.ts | 31 +++++++++++++++++++++++++++++++
src/testing/index.ts | 2 ++
10 files changed, 49 insertions(+), 6 deletions(-)
create mode 100644 src/testing/debug.ts
diff --git a/src/api/debug/certificates.ts b/src/api/debug/certificates.ts
index e8fa7eae6..2a36452a8 100644
--- a/src/api/debug/certificates.ts
+++ b/src/api/debug/certificates.ts
@@ -170,7 +170,7 @@ export async function setup(connection: IBMi, imported?: ImportedCertificate) {
debugConfig.delete("DEBUG_SERVICE_KEYSTORE_PASSWORD");
await debugConfig.save();
}
- const javaHome = getJavaHome((await getDebugServiceDetails()).java);
+ const javaHome = getJavaHome(connection, (await getDebugServiceDetails()).java);
const encryptResult = await connection.sendCommand({
command: `${path.posix.join(debugConfig.getRemoteServiceBin(), `encryptKeystorePassword.sh`)} | /usr/bin/tail -n 1`,
env: {
@@ -268,7 +268,7 @@ export async function sanityCheck(connection: IBMi, content: IBMiContent) {
//Check if java home needs to be updated if the service got updated (e.g: v1 uses Java 8 and v2 uses Java 11)
const javaHome = debugConfig.get("JAVA_HOME");
- const expectedJavaHome = getJavaHome((await getDebugServiceDetails()).java);
+ const expectedJavaHome = getJavaHome(connection, (await getDebugServiceDetails()).java);
if (javaHome && javaHome !== expectedJavaHome) {
if (await content.testStreamFile(DEBUG_CONFIG_FILE, "w")) {
//Automatically make the change if possible
diff --git a/src/api/debug/config.ts b/src/api/debug/config.ts
index 324402d40..d399de841 100644
--- a/src/api/debug/config.ts
+++ b/src/api/debug/config.ts
@@ -2,6 +2,7 @@ import path from "path";
import vscode from "vscode";
import { instance } from "../../instantiate";
import { t } from "../../locale";
+import IBMi from "../IBMi";
import { SERVICE_CERTIFICATE } from "./certificates";
type ConfigLine = {
@@ -170,9 +171,12 @@ export async function getDebugServiceDetails(): Promise {
return debugServiceDetails;
}
-export function getJavaHome(version: string) {
- switch (version) {
- case "11": return `/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit`;
- default: return `/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`;
+export function getJavaHome(connection: IBMi, version: string) {
+ version = version.padEnd(2, '0');
+ const javaHome = connection.remoteFeatures[`jdk${version}`];
+ if (!javaHome) {
+ throw new Error(t('java.not.found', version));
}
+
+ return javaHome;
}
\ No newline at end of file
diff --git a/src/locale/ids/da.json b/src/locale/ids/da.json
index ab549a1c8..73a6839c3 100644
--- a/src/locale/ids/da.json
+++ b/src/locale/ids/da.json
@@ -224,6 +224,7 @@
"ifsBrowser.uploadStreamfile.select.type.title": "Hvad ønsker du at uploade?",
"ifsBrowser.uploadStreamfile.uploadedFiles": "Upload er udført.",
"JAVA_HOME": "Java Home",
+ "java.not.found":"Java version {0} is not installed.",
"job": "Job",
"JOB_NAME_SHORT": "Job navn",
"JOB_NUMBER": "Job nummer",
diff --git a/src/locale/ids/de.json b/src/locale/ids/de.json
index 34c686177..0fae41858 100644
--- a/src/locale/ids/de.json
+++ b/src/locale/ids/de.json
@@ -224,6 +224,7 @@
"ifsBrowser.uploadStreamfile.select.type.title": "Was möchten Sie hochladen?",
"ifsBrowser.uploadStreamfile.uploadedFiles": "Hochladen abgeschlossen.",
"JAVA_HOME": "Java-Home Pfad",
+ "java.not.found":"Java version {0} is not installed.",
"job": "Job",
"JOB_NAME_SHORT": "Job Name",
"JOB_NUMBER": "Job Nummer",
diff --git a/src/locale/ids/en.json b/src/locale/ids/en.json
index d73988885..ee6b11c5b 100644
--- a/src/locale/ids/en.json
+++ b/src/locale/ids/en.json
@@ -224,6 +224,7 @@
"ifsBrowser.uploadStreamfile.select.type.title": "What do you want to upload?",
"ifsBrowser.uploadStreamfile.uploadedFiles": "Upload completed.",
"JAVA_HOME": "Java Home",
+ "java.not.found":"Java version {0} is not installed.",
"job": "Job",
"JOB_NAME_SHORT": "Job name",
"JOB_NUMBER": "Job number",
diff --git a/src/locale/ids/fr.json b/src/locale/ids/fr.json
index 3ef4b6849..bc59252ba 100644
--- a/src/locale/ids/fr.json
+++ b/src/locale/ids/fr.json
@@ -224,6 +224,7 @@
"ifsBrowser.uploadStreamfile.select.type.title": "Que souhaitez-vous envoyer?",
"ifsBrowser.uploadStreamfile.uploadedFiles": "Envoi terminée.",
"JAVA_HOME": "Java Home",
+ "java.not.found":"Java version {0} n'est pas installé.",
"job": "Job",
"JOB_NAME_SHORT": "Nom du job",
"JOB_NUMBER": "Numéro du job",
diff --git a/src/locale/ids/no.json b/src/locale/ids/no.json
index 7fdc97dab..c38a9ca7e 100644
--- a/src/locale/ids/no.json
+++ b/src/locale/ids/no.json
@@ -224,6 +224,7 @@
"ifsBrowser.uploadStreamfile.select.type.title": "Hva ønsker du å uploade?",
"ifsBrowser.uploadStreamfile.uploadedFiles": "Upload er utført.",
"JAVA_HOME": "Java Home",
+ "java.not.found":"Java version {0} is not installed.",
"job": "Jobb",
"JOB_NAME_SHORT": "Jobb navn",
"JOB_NUMBER": "Jobb nummer",
diff --git a/src/locale/ids/pl.json b/src/locale/ids/pl.json
index 50b762bf5..fa79eb6c5 100644
--- a/src/locale/ids/pl.json
+++ b/src/locale/ids/pl.json
@@ -224,6 +224,7 @@
"ifsBrowser.uploadStreamfile.select.type.title": "Co chcesz przesłać?",
"ifsBrowser.uploadStreamfile.uploadedFiles": "Przesyłanie zakończone.",
"JAVA_HOME": "Java Home",
+ "java.not.found":"Java version {0} is not installed.",
"job": "Zadanie",
"JOB_NAME_SHORT": "Nazwa zadania`",
"JOB_NUMBER": "Numer zadania",
diff --git a/src/testing/debug.ts b/src/testing/debug.ts
new file mode 100644
index 000000000..709091cb0
--- /dev/null
+++ b/src/testing/debug.ts
@@ -0,0 +1,31 @@
+import assert from "assert";
+import { TestSuite } from ".";
+import { getJavaHome } from "../api/debug/config";
+import { instance } from "../instantiate";
+
+export const DebugSuite: TestSuite = {
+ name: `Debug engine tests`,
+ tests: [
+ {
+ name: "Check Java versions", test: async () => {
+ const connection = instance.getConnection()!;
+ if(connection.remoteFeatures.jdk80){
+ const jdk8 = getJavaHome(connection, '8');
+ assert.strictEqual(jdk8, connection.remoteFeatures.jdk80);
+ }
+
+ if(connection.remoteFeatures.jdk11){
+ const jdk11 = getJavaHome(connection, '11');
+ assert.strictEqual(jdk11, connection.remoteFeatures.jdk11);
+ }
+
+ if(connection.remoteFeatures.jdk17){
+ const jdk11 = getJavaHome(connection, '17');
+ assert.strictEqual(jdk11, connection.remoteFeatures.jdk11);
+ }
+
+ assert.throws(() => getJavaHome(connection, '666'));
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/testing/index.ts b/src/testing/index.ts
index 8ea80a0c4..b4c76b3c3 100644
--- a/src/testing/index.ts
+++ b/src/testing/index.ts
@@ -5,6 +5,7 @@ import { ActionSuite } from "./action";
import { ComponentSuite } from "./components";
import { ConnectionSuite } from "./connection";
import { ContentSuite } from "./content";
+import { DebugSuite } from "./debug";
import { DeployToolsSuite } from "./deployTools";
import { EncodingSuite } from "./encoding";
import { FilterSuite } from "./filter";
@@ -18,6 +19,7 @@ const suites: TestSuite[] = [
ActionSuite,
ConnectionSuite,
ContentSuite,
+ DebugSuite,
DeployToolsSuite,
ToolsSuite,
ILEErrorSuite,
From d3a5e508499af224464f4ab7cd736bef7730ebf9 Mon Sep 17 00:00:00 2001
From: Seb Julliand
Date: Mon, 7 Oct 2024 22:15:56 +0200
Subject: [PATCH 17/28] Fix debug java test
Signed-off-by: Seb Julliand
---
src/testing/debug.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/testing/debug.ts b/src/testing/debug.ts
index 709091cb0..44aa2e89d 100644
--- a/src/testing/debug.ts
+++ b/src/testing/debug.ts
@@ -21,7 +21,7 @@ export const DebugSuite: TestSuite = {
if(connection.remoteFeatures.jdk17){
const jdk11 = getJavaHome(connection, '17');
- assert.strictEqual(jdk11, connection.remoteFeatures.jdk11);
+ assert.strictEqual(jdk11, connection.remoteFeatures.jdk17);
}
assert.throws(() => getJavaHome(connection, '666'));
From 449783f75f56f36f093213b2e770aa1db5570b75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Julliand?=
Date: Tue, 8 Oct 2024 18:51:17 +0200
Subject: [PATCH 18/28] Do not use interpolation when not needed
Co-authored-by: LJ <3708366+worksofliam@users.noreply.github.com>
---
src/api/IBMi.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 789074727..e9159b409 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -596,7 +596,7 @@ export default class IBMi {
progress.report({
message: `Checking installed components on host IBM i: Java`
});
- const javaCheck = async (root: string) => await this.content.testStreamFile(`${root}/bin/java`, 'x') ? `${root}` : undefined;
+ const javaCheck = async (root: string) => await this.content.testStreamFile(`${root}/bin/java`, 'x') ? root : undefined;
this.remoteFeatures.jdk80 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`);
this.remoteFeatures.jdk11 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit`);
this.remoteFeatures.openjdk11 = await javaCheck(`/QOpensys/pkgs/lib/jvm/openjdk-11`);
From 32e4566fddf25e4b10273c0b3545e059d3c1646b Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Thu, 10 Oct 2024 10:01:21 -0400
Subject: [PATCH 19/28] Bump to 2.13.1
Signed-off-by: worksofliam
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index c33095398..ddf92d944 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"icon": "icon.png",
"displayName": "Code for IBM i",
"description": "Maintain your RPGLE, CL, COBOL, C/CPP on IBM i right from Visual Studio Code.",
- "version": "2.13.4-dev.0",
+ "version": "2.13.1",
"keywords": [
"ibmi",
"rpgle",
From f7da8212168452590d16c325bae2a364d21cddc3 Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Thu, 10 Oct 2024 10:18:50 -0400
Subject: [PATCH 20/28] Fix type issue in Search
Signed-off-by: worksofliam
---
src/api/Search.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/api/Search.ts b/src/api/Search.ts
index 5189b0991..ce2254c5c 100644
--- a/src/api/Search.ts
+++ b/src/api/Search.ts
@@ -63,7 +63,7 @@ export namespace Search {
} else {
// Else, we need to fetch the member info for each hit so we can display the correct extension
- const infoComponent = connection?.getComponent(`GetMemberInfo`);
+ const infoComponent = connection?.getComponent(GetMemberInfo);
const memberInfos: IBMiMember[] = hits.map(hit => {
const { name, dir } = path.parse(hit.path);
const [library, file] = dir.split(`/`);
From fea455d5edae6b60b5f265900dfa5c8489417fb2 Mon Sep 17 00:00:00 2001
From: worksofliam
Date: Thu, 10 Oct 2024 10:20:56 -0400
Subject: [PATCH 21/28] Bump to 2.13.2
Signed-off-by: worksofliam
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index ddf92d944..46078173c 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"icon": "icon.png",
"displayName": "Code for IBM i",
"description": "Maintain your RPGLE, CL, COBOL, C/CPP on IBM i right from Visual Studio Code.",
- "version": "2.13.1",
+ "version": "2.13.2",
"keywords": [
"ibmi",
"rpgle",
From 6c4f0fd9b499e4a4ee7d7d9df2a26c7fed7c2c5f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Thu, 10 Oct 2024 14:22:41 +0000
Subject: [PATCH 22/28] Release 2.13.2
---
package-lock.json | 4 ++--
types/package-lock.json | 4 ++--
types/package.json | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 305a1238b..9fd7962fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "code-for-ibmi",
- "version": "2.13.4-dev.0",
+ "version": "2.13.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "code-for-ibmi",
- "version": "2.13.4-dev.0",
+ "version": "2.13.2",
"license": "MIT",
"dependencies": {
"@bendera/vscode-webview-elements": "^0.12.0",
diff --git a/types/package-lock.json b/types/package-lock.json
index bb8713f8e..6fe9f0788 100644
--- a/types/package-lock.json
+++ b/types/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@halcyontech/vscode-ibmi-types",
- "version": "2.13.4-dev.0",
+ "version": "2.13.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@halcyontech/vscode-ibmi-types",
- "version": "2.13.4-dev.0",
+ "version": "2.13.2",
"license": "ISC"
}
}
diff --git a/types/package.json b/types/package.json
index 1387b1b83..f6d1f35ee 100644
--- a/types/package.json
+++ b/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@halcyontech/vscode-ibmi-types",
- "version": "2.13.4-dev.0",
+ "version": "2.13.2",
"description": "Types for vscode-ibmi",
"typings": "./typings.d.ts",
"scripts": {
From 0acd262be78a7b309a5e9bf4b9a0a3817065e4f5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Thu, 10 Oct 2024 14:22:42 +0000
Subject: [PATCH 23/28] Starting 2.13.3-dev.0 development
---
package-lock.json | 4 ++--
package.json | 2 +-
types/package-lock.json | 4 ++--
types/package.json | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 9fd7962fe..0b7016888 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "code-for-ibmi",
- "version": "2.13.2",
+ "version": "2.13.3-dev.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "code-for-ibmi",
- "version": "2.13.2",
+ "version": "2.13.3-dev.0",
"license": "MIT",
"dependencies": {
"@bendera/vscode-webview-elements": "^0.12.0",
diff --git a/package.json b/package.json
index 46078173c..33ed5241f 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"icon": "icon.png",
"displayName": "Code for IBM i",
"description": "Maintain your RPGLE, CL, COBOL, C/CPP on IBM i right from Visual Studio Code.",
- "version": "2.13.2",
+ "version": "2.13.3-dev.0",
"keywords": [
"ibmi",
"rpgle",
diff --git a/types/package-lock.json b/types/package-lock.json
index 6fe9f0788..76d3b71cf 100644
--- a/types/package-lock.json
+++ b/types/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@halcyontech/vscode-ibmi-types",
- "version": "2.13.2",
+ "version": "2.13.3-dev.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@halcyontech/vscode-ibmi-types",
- "version": "2.13.2",
+ "version": "2.13.3-dev.0",
"license": "ISC"
}
}
diff --git a/types/package.json b/types/package.json
index f6d1f35ee..98131eb54 100644
--- a/types/package.json
+++ b/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@halcyontech/vscode-ibmi-types",
- "version": "2.13.2",
+ "version": "2.13.3-dev.0",
"description": "Types for vscode-ibmi",
"typings": "./typings.d.ts",
"scripts": {
From 685141f94a3eb163d307a0bd464d05d2bb01aa2e Mon Sep 17 00:00:00 2001
From: krethan
Date: Sat, 12 Oct 2024 21:14:24 -0600
Subject: [PATCH 24/28] Reuse checkObject from content and moved deleteObject
into content. Fixed indentation from 4 characters to 2.
---
src/api/IBMi.ts | 8 +-
src/api/IBMiApps.ts | 136 ++++----
src/api/IBMiContent.ts | 14 +-
src/api/IBMiSettings.ts | 720 +++++++++++++++++++---------------------
4 files changed, 433 insertions(+), 445 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 13ba090b2..71e1948fb 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -325,7 +325,7 @@ export default class IBMi {
message: `Checking for bad data areas.`
});
- const QCPTOIMPF = await connSettings.checkObjectExists('QSYS', 'QCPTOIMPF', '*DTAARA');
+ const QCPTOIMPF = await this.content.checkObject({ library: 'QSYS', name: 'QCPTOIMPF', type: '*DTAARA' });
if (QCPTOIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPTOIMPF exists on this system and may impact Code for IBM i functionality.`, {
@@ -334,7 +334,7 @@ export default class IBMi {
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- connSettings.deleteObject('QSYS', 'QCPTOIMPF', '*DTAARA').then((result) => {
+ this.content.deleteObject({ library: 'QSYS', name: 'QCPTOIMPF', type: '*DTAARA' }).then((result) => {
if (result) {
vscode.window.showInformationMessage(`The data
area QSYS/QCPTOIMPF has been deleted.`);
@@ -353,7 +353,7 @@ export default class IBMi {
});
}
- const QCPFRMIMPF = await connSettings.checkObjectExists('QSYS', 'QCPFRMIMPF', '*DTAARA');
+ const QCPFRMIMPF = await this.content.checkObject({ library: 'QSYS', name: 'QCPFRMIMPF', type: '*DTAARA' });
if (QCPFRMIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPFRMIMPF exists on this system and may impact Code for IBM i functionality.`, {
@@ -361,7 +361,7 @@ export default class IBMi {
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- connSettings.deleteObject('QSYS', 'QCPFRMIMPF', '*DTAARA')
+ this.content.deleteObject({ library: 'QSYS', name: 'QCPFRMIMPF', type: '*DTAARA' })
.then((result) => {
if (result) {
vscode.window.showInformationMessage(`The data area QSYS/QCPFRMIMPF has been deleted.`);
diff --git a/src/api/IBMiApps.ts b/src/api/IBMiApps.ts
index abfc48974..b5527c122 100644
--- a/src/api/IBMiApps.ts
+++ b/src/api/IBMiApps.ts
@@ -3,85 +3,85 @@ import { RemoteApp, RemoteApps, RemoteFeatures } from "../typings";
export default class IBMiApps {
- private remoteApps: RemoteApps;
- private remoteFeatures: RemoteFeatures;
-
- constructor() {
- this.remoteApps = [
- {
- path: `/usr/bin/`,
- names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
- },
- {
- path: `/QOpenSys/pkgs/bin/`,
- names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
- },
- {
- path: `/QSYS.LIB/`,
- // In the future, we may use a generic specific.
- // Right now we only need one program
- // specific: `*.PGM`,
- specific: `QZDFMDB2.PGM`,
- names: [`QZDFMDB2.PGM`]
- },
- {
- path: `/QIBM/ProdData/IBMiDebugService/bin/`,
- specific: `startDebugService.sh`,
- names: [`startDebugService.sh`]
- }
- ];
-
- this.remoteFeatures = {};
- this.setRemoteFeatures();
-
+ private remoteApps: RemoteApps;
+ private remoteFeatures: RemoteFeatures;
+
+ constructor() {
+ this.remoteApps = [
+ {
+ path: `/usr/bin/`,
+ names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
+ },
+ {
+ path: `/QOpenSys/pkgs/bin/`,
+ names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
+ },
+ {
+ path: `/QSYS.LIB/`,
+ // In the future, we may use a generic specific.
+ // Right now we only need one program
+ // specific: `*.PGM`,
+ specific: `QZDFMDB2.PGM`,
+ names: [`QZDFMDB2.PGM`]
+ },
+ {
+ path: `/QIBM/ProdData/IBMiDebugService/bin/`,
+ specific: `startDebugService.sh`,
+ names: [`startDebugService.sh`]
+ }
+ ];
+
+ this.remoteFeatures = {};
+ this.initRemoteFeatures();
+
+ }
+
+ addRemoteApp(remoteApp: RemoteApp) {
+
+ //Add remote App
+ this.remoteApps.push(remoteApp);
+
+ //Add possible features to list
+ for (const name of remoteApp.names) {
+ this.remoteFeatures[name] = undefined;
}
- addRemoteApp(remoteApp: RemoteApp) {
-
- //Add remote App
- this.remoteApps.push(remoteApp);
+ }
- //Add possible features to list
- for(const name of remoteApp.names) {
- this.remoteFeatures[name] = undefined;
- }
+ getRemoteApps(): RemoteApps {
+ return this.remoteApps;
+ }
- }
+ initRemoteFeatures() {
- getRemoteApps(): RemoteApps {
- return this.remoteApps;
+ for (const feature of this.remoteApps) {
+ for (const name of feature.names) {
+ this.remoteFeatures[name] = undefined;
+ }
}
- setRemoteFeatures() {
+ }
- for (const feature of this.remoteApps) {
- for(const name of feature.names) {
- this.remoteFeatures[name] = undefined;
- }
- }
-
- }
+ getRemoteFeatures(): RemoteFeatures {
+ return this.remoteFeatures;
+ }
- getRemoteFeatures(): RemoteFeatures {
- return this.remoteFeatures;
- }
+ async checkRemoteFeatures(remoteApp: RemoteApp, connection: IBMi) {
- async checkRemoteFeatures(remoteApp: RemoteApp, connection: IBMi) {
-
- const call = await connection.sendCommand({ command: `ls -p ${remoteApp.path}${remoteApp.specific || ``}` });
- if (call.stdout) {
- const files = call.stdout.split(`\n`);
-
- if (remoteApp.specific) {
- for (const name of remoteApp.names)
- this.remoteFeatures[name] = files.find(file => file.includes(name));
- } else {
- for (const name of remoteApp.names)
- if (files.includes(name))
- this.remoteFeatures[name] = remoteApp.path + name;
- }
- }
+ const call = await connection.sendCommand({ command: `ls -p ${remoteApp.path}${remoteApp.specific || ``}` });
+ if (call.stdout) {
+ const files = call.stdout.split(`\n`);
+ if (remoteApp.specific) {
+ for (const name of remoteApp.names)
+ this.remoteFeatures[name] = files.find(file => file.includes(name));
+ } else {
+ for (const name of remoteApp.names)
+ if (files.includes(name))
+ this.remoteFeatures[name] = remoteApp.path + name;
+ }
}
+ }
+
}
\ No newline at end of file
diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts
index 0775b561a..1a5a1c096 100644
--- a/src/api/IBMiContent.ts
+++ b/src/api/IBMiContent.ts
@@ -906,6 +906,18 @@ export default class IBMiContent {
})).code === 0;
}
+ async deleteObject(object: { library: string, name: string, type: string }) {
+
+ return (await this.ibmi.runCommand({
+ command: this.toCl(`DLTOBJ`, {
+ obj: `${this.ibmi.upperCaseName(object.library)}/${this.ibmi.upperCaseName(object.name)}`,
+ objtype: object.type.toLocaleUpperCase()
+ }),
+ noLibList: true
+ })).code === 0;
+
+ }
+
async testStreamFile(path: string, right: "e" | "f" | "d" | "r" | "w" | "x") {
return (await this.ibmi.sendCommand({ command: `test -${right} ${Tools.escapePath(path)}` })).code === 0;
}
@@ -951,7 +963,7 @@ export default class IBMiContent {
return cl;
}
- async getAttributes(path: string | (QsysPath & { member?: string }), ...operands: AttrOperands[]) {
+ async getAttributes(path: string | (QsysPath & { member?: string }), ...operands: AttrOperands[]) {
const target = (path = typeof path === 'string' ? Tools.escapePath(path) : this.ibmi.sysNameInAmerican(Tools.qualifyPath(path.library, path.name, path.member, path.asp)));
const result = await this.ibmi.sendCommand({ command: `${this.ibmi.remoteFeatures.attr} -p ${target} ${operands.join(" ")}` });
diff --git a/src/api/IBMiSettings.ts b/src/api/IBMiSettings.ts
index c3eb961a9..e0b3755ab 100644
--- a/src/api/IBMiSettings.ts
+++ b/src/api/IBMiSettings.ts
@@ -7,507 +7,483 @@ const CCSID_SYSVAL = -2;
export default class IBMiSettings {
- constructor(private connection: IBMi) {
+ constructor(private connection: IBMi) {
- }
+ }
- async checkShellOutput(): Promise {
+ async checkShellOutput(): Promise {
- const checkShellText = `This should be the only text!`;
- const checkShellResult = await this.connection.sendCommand({
- command: `echo "${checkShellText}"`,
- directory: `.`
- });
+ const checkShellText = `This should be the only text!`;
+ const checkShellResult = await this.connection.sendCommand({
+ command: `echo "${checkShellText}"`,
+ directory: `.`
+ });
- return Promise.resolve(checkShellResult.stdout.split(`\n`)[0] == checkShellText);
+ return Promise.resolve(checkShellResult.stdout.split(`\n`)[0] == checkShellText);
- }
+ }
- async getHomeDirectory(): Promise<{ homeExists: boolean, homeDir: string, homeMsg: string }> {
+ async getHomeDirectory(): Promise<{ homeExists: boolean, homeDir: string, homeMsg: string }> {
- let homeDir;
- let homeMsg = '';
- let homeExists;
+ let homeDir;
+ let homeMsg = '';
+ let homeExists;
- const homeResult = await this.connection.sendCommand({
- command: `echo $HOME && cd && test -w $HOME`,
- directory: `.`
- });
+ const homeResult = await this.connection.sendCommand({
+ command: `echo $HOME && cd && test -w $HOME`,
+ directory: `.`
+ });
- // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
- // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
- // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
- // if we iterate on this code again in the future)
- // - stdout contains the name of the home directory (even if it does not exist)
- // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
- // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
-
- homeExists = homeResult.code == 0;
- homeDir = homeResult.stdout.trim();
-
- if (!homeExists) {
- // Let's try to provide more valuable information to the user about why their home directory
- // is bad and maybe even provide the opportunity to create the home directory
-
- // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
- homeExists = (0 === (await this.connection.sendCommand({ command: `test -e ${homeDir}` })).code);
- if (homeExists) {
- // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
- // But, remember, but we only got here if 'cd $HOME' failed.
- // Let's try to figure out why....
- if (0 !== (await this.connection.sendCommand({ command: `test -d ${homeDir}` })).code) {
- homeMsg = `Your home directory (${homeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`;
- }
- else if (0 !== (await this.connection.sendCommand({ command: `test -w ${homeDir}` })).code) {
- homeMsg = `Your home directory (${homeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`;
-
- }
- else if (0 !== (await this.connection.sendCommand({ command: `test -x ${homeDir}` })).code) {
- homeMsg = `Your home directory (${homeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`;
- }
- else {
- // not sure, but get your sys admin involved
- homeMsg = `Your home directory (${homeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`;
- }
- }
- else {
- homeMsg = `Your home directory (${homeDir}) does not exist, so Code for IBM i may not function correctly.`;
- }
- }
+ // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
+ // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
+ // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
+ // if we iterate on this code again in the future)
+ // - stdout contains the name of the home directory (even if it does not exist)
+ // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
+ // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
+
+ homeExists = homeResult.code == 0;
+ homeDir = homeResult.stdout.trim();
+
+ if (!homeExists) {
+ // Let's try to provide more valuable information to the user about why their home directory
+ // is bad and maybe even provide the opportunity to create the home directory
- return Promise.resolve({ homeExists, homeDir, homeMsg });
+ // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
+ homeExists = (0 === (await this.connection.sendCommand({ command: `test -e ${homeDir}` })).code);
+ if (homeExists) {
+ // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
+ // But, remember, but we only got here if 'cd $HOME' failed.
+ // Let's try to figure out why....
+ if (0 !== (await this.connection.sendCommand({ command: `test -d ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -w ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -x ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else {
+ // not sure, but get your sys admin involved
+ homeMsg = `Your home directory (${homeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ }
+ else {
+ homeMsg = `Your home directory (${homeDir}) does not exist, so Code for IBM i may not function correctly.`;
+ }
}
- async createHomeDirectory(homeDir: string, username: string): Promise<{ homeCreated: boolean, homeMsg: string }> {
+ return Promise.resolve({ homeExists, homeDir, homeMsg });
- let homeCreated = false;
- let homeMsg = '';
+ }
- const homeCmd = `mkdir -p ${homeDir} && chown ${username.toLowerCase()} ${homeDir} && chmod 0755 ${homeDir}`;
+ async createHomeDirectory(homeDir: string, username: string): Promise<{ homeCreated: boolean, homeMsg: string }> {
- let mkHomeResult = await this.connection.sendCommand({ command: homeCmd, directory: `.` });
+ let homeCreated = false;
+ let homeMsg = '';
- if (0 === mkHomeResult.code) {
- homeCreated = true;
- } else {
- let mkHomeErrs = mkHomeResult.stderr;
- // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
- homeMsg = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
- }
+ const homeCmd = `mkdir -p ${homeDir} && chown ${username.toLowerCase()} ${homeDir} && chmod 0755 ${homeDir}`;
- return Promise.resolve({ homeCreated, homeMsg });
+ let mkHomeResult = await this.connection.sendCommand({ command: homeCmd, directory: `.` });
+ if (0 === mkHomeResult.code) {
+ homeCreated = true;
+ } else {
+ let mkHomeErrs = mkHomeResult.stderr;
+ // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
+ homeMsg = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
}
- async getLibraryList(): Promise<{ libStatus: boolean, currentLibrary: string, defaultUserLibraries: string[] }> {
-
+ return Promise.resolve({ homeCreated, homeMsg });
+ }
- //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
- //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
+ async getLibraryList(): Promise<{ libStatus: boolean, currentLibrary: string, defaultUserLibraries: string[] }> {
- let currentLibrary = `QGPL`;
- let defaultUserLibraries = [];
- let libStatus = false;
- const liblResult = await this.connection.sendQsh({
- command: `liblist`
- });
- if (liblResult.code === 0) {
- libStatus = true;
- const libraryListString = liblResult.stdout;
- if (libraryListString !== ``) {
- const libraryList = libraryListString.split(`\n`);
-
- let lib, type;
- for (const line of libraryList) {
- lib = line.substring(0, 10).trim();
- type = line.substring(12);
-
- switch (type) {
- case `USR`:
- defaultUserLibraries.push(lib);
- break;
-
- case `CUR`:
- currentLibrary = lib;
- break;
- }
- }
- }
- }
+ //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
+ //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
- return Promise.resolve({ libStatus, currentLibrary, defaultUserLibraries });
+ let currentLibrary = `QGPL`;
+ let defaultUserLibraries = [];
+ let libStatus = false;
- }
+ const liblResult = await this.connection.sendQsh({
+ command: `liblist`
+ });
- async setTempLibrary(tempLibrary: string): Promise {
+ if (liblResult.code === 0) {
+ libStatus = true;
+ const libraryListString = liblResult.stdout;
+ if (libraryListString !== ``) {
+ const libraryList = libraryListString.split(`\n`);
- let tempLibrarySet = false;
+ let lib, type;
+ for (const line of libraryList) {
+ lib = line.substring(0, 10).trim();
+ type = line.substring(12);
- //Check the temp lib (where temp outfile data lives) exists
- const createdTempLib = await this.connection.runCommand({
- command: `CRTLIB LIB(${tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
- noLibList: true
- });
+ switch (type) {
+ case `USR`:
+ defaultUserLibraries.push(lib);
+ break;
- if (createdTempLib.code === 0) {
- tempLibrarySet = true;
+ case `CUR`:
+ currentLibrary = lib;
+ break;
+ }
}
- else {
- const messages = Tools.parseMessages(createdTempLib.stderr);
- if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
- tempLibrarySet = true;
- }
- else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
- const tempLibExists = await this.connection.runCommand({
- command: `CHKOBJ OBJ(QSYS/${tempLibrary}) OBJTYPE(*LIB)`,
- noLibList: true
- });
-
- if (tempLibExists.code === 0) {
- //We're all good if no errors
- tempLibrarySet = true;
- }
- else {
- tempLibrarySet = false;
- }
+ }
+ }
- }
- }
+ return Promise.resolve({ libStatus, currentLibrary, defaultUserLibraries });
- return Promise.resolve(tempLibrarySet);
+ }
- }
+ async setTempLibrary(tempLibrary: string): Promise {
- async setTempDirectory(tempDir: string): Promise {
+ let tempLibrarySet = false;
- let tempDirSet = false;
+ //Check the temp lib (where temp outfile data lives) exists
+ const createdTempLib = await this.connection.runCommand({
+ command: `CRTLIB LIB(${tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
+ noLibList: true
+ });
- // Check if the temp directory exists
- let result = await this.connection.sendCommand({
- command: `[ -d "${tempDir}" ]`
+ if (createdTempLib.code === 0) {
+ tempLibrarySet = true;
+ }
+ else {
+ const messages = Tools.parseMessages(createdTempLib.stderr);
+ if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
+ tempLibrarySet = true;
+ }
+ else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
+ const tempLibExists = await this.connection.runCommand({
+ command: `CHKOBJ OBJ(QSYS/${tempLibrary}) OBJTYPE(*LIB)`,
+ noLibList: true
});
- if (result.code === 0) {
- // Directory exists
- tempDirSet = true;
- } else {
- // Directory does not exist, try to create it
- let result = await this.connection.sendCommand({
- command: `mkdir -p ${tempDir}`
- });
- if (result.code === 0) {
- // Directory created
- tempDirSet = true;
- } else {
- // Directory not created
- }
+ if (tempLibExists.code === 0) {
+ //We're all good if no errors
+ tempLibrarySet = true;
+ }
+ else {
+ tempLibrarySet = false;
}
- return Promise.resolve(tempDirSet);
+ }
+ }
+ return Promise.resolve(tempLibrarySet);
+
+ }
+
+ async setTempDirectory(tempDir: string): Promise {
+
+ let tempDirSet = false;
+
+ // Check if the temp directory exists
+ let result = await this.connection.sendCommand({
+ command: `[ -d "${tempDir}" ]`
+ });
+
+ if (result.code === 0) {
+ // Directory exists
+ tempDirSet = true;
+ } else {
+ // Directory does not exist, try to create it
+ let result = await this.connection.sendCommand({
+ command: `mkdir -p ${tempDir}`
+ });
+ if (result.code === 0) {
+ // Directory created
+ tempDirSet = true;
+ } else {
+ // Directory not created
+ }
}
- async clearTempLibrary(tempLibrary: string): Promise {
+ return Promise.resolve(tempDirSet);
- let clearMsg = '';
+ }
- this.connection.runCommand({
- command: `DLTOBJ OBJ(${tempLibrary}/O_*) OBJTYPE(*FILE)`,
- noLibList: true,
- })
- .then(result => {
- // All good!
- if (result && result.stderr) {
- const messages = Tools.parseMessages(result.stderr);
- if (!messages.findId(`CPF2125`)) {
- clearMsg = `Temporary data not cleared from ${tempLibrary}.`;
- }
- }
- });
+ async clearTempLibrary(tempLibrary: string): Promise {
- return Promise.resolve(clearMsg);
+ let clearMsg = '';
- }
+ this.connection.runCommand({
+ command: `DLTOBJ OBJ(${tempLibrary}/O_*) OBJTYPE(*FILE)`,
+ noLibList: true,
+ })
+ .then(result => {
+ // All good!
+ if (result && result.stderr) {
+ const messages = Tools.parseMessages(result.stderr);
+ if (!messages.findId(`CPF2125`)) {
+ clearMsg = `Temporary data not cleared from ${tempLibrary}.`;
+ }
+ }
+ });
- async clearTempDirectory(tempDir: string): Promise {
+ return Promise.resolve(clearMsg);
- let clearMsg = '';
+ }
- this.connection.sendCommand({
- command: `rm -rf ${path.posix.join(tempDir, `vscodetemp*`)}`
- })
- .then(result => {
- // All good!
- })
- .catch(e => {
- // CPF2125: No objects deleted.
- clearMsg = `Temporary data not cleared from ${tempDir}.`;
- });
+ async clearTempDirectory(tempDir: string): Promise {
- return Promise.resolve(clearMsg);
+ let clearMsg = '';
+ try {
+ this.connection.sendCommand({
+ command: `rm -rf ${path.posix.join(tempDir, `vscodetemp*`)}`
+ });
+ }
+ catch (e) {
+ // CPF2125: No objects deleted.
+ clearMsg = `Temporary data not cleared from ${tempDir}.`;
}
- async checkObjectExists(library: string, object: string, type: string): Promise {
-
- const objResult = await this.connection.runCommand({
- command: `CHKOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
- noLibList: true
- });
+ return Promise.resolve(clearMsg);
- return Promise.resolve(objResult.code === 0);
+ }
- }
+ async getASPInfo(): Promise {
- async deleteObject(library: string, object: string, type: string): Promise {
+ let aspInfo: aspInfo = {};
- const deleteResult = await this.connection.runCommand({
- command: `DLTOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
- noLibList: true
+ try {
+ const resultSet = await this.connection.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
+ if (resultSet.length) {
+ resultSet.forEach(row => {
+ if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
+ aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
+ }
});
-
- return Promise.resolve(deleteResult.code === 0);
-
+ }
+ } catch (e) {
+ //Oh well
+ return Promise.reject(e);
}
- async getASPInfo(): Promise {
+ return Promise.resolve(aspInfo);
- let aspInfo: aspInfo = {};
+ }
- try {
- const resultSet = await this.connection.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
- if (resultSet.length) {
- resultSet.forEach(row => {
- if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
- aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
- }
- });
- }
- } catch (e) {
- //Oh well
- return Promise.reject(e);
- }
+ async getQCCSID(): Promise {
- return Promise.resolve(aspInfo);
+ let qccsid = 0;
+ const [systemCCSID] = await this.connection.runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
+ if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
+ qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
}
- async getQCCSID(): Promise {
+ return Promise.resolve(qccsid);
- let qccsid = 0;
+ }
- const [systemCCSID] = await this.connection.runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
- if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
- qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
- }
+ async getjobCCSID(userName: string): Promise {
- return Promise.resolve(qccsid);
+ let jobCCSID = CCSID_SYSVAL;
+ const [userInfo] = await this.connection.runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${userName}') ) )`);
+ if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
+ jobCCSID = userInfo.CHARACTER_CODE_SET_ID;
}
- async getjobCCSID(userName: string): Promise {
+ return Promise.resolve(jobCCSID);
- let jobCCSID = CCSID_SYSVAL;
+ }
- const [userInfo] = await this.connection.runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${userName}') ) )`);
- if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
- jobCCSID = userInfo.CHARACTER_CODE_SET_ID;
- }
+ async getDefaultCCSID(): Promise {
- return Promise.resolve(jobCCSID);
+ let userDefaultCCSID = 0;
+ try {
+ const [activeJob] = await this.connection.runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
+ userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
+ }
+ catch (error) {
+ const [defaultCCSID] = (await this.connection.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
+ .stdout
+ .split("\n")
+ .filter(line => line.includes("DFTCCSID"));
+
+ const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
+ if (defaultCCSCID && !isNaN(defaultCCSCID)) {
+ userDefaultCCSID = defaultCCSCID;
+ }
}
- async getDefaultCCSID(): Promise {
+ return Promise.resolve(userDefaultCCSID);
- let userDefaultCCSID = 0;
+ }
- try {
- const [activeJob] = await this.connection.runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
- userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
- }
- catch (error) {
- const [defaultCCSID] = (await this.connection.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
- .stdout
- .split("\n")
- .filter(line => line.includes("DFTCCSID"));
-
- const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
- if (defaultCCSCID && !isNaN(defaultCCSCID)) {
- userDefaultCCSID = defaultCCSCID;
- }
- }
+ async getLocalEncodingValues(): Promise {
- return Promise.resolve(userDefaultCCSID);
+ let localEncoding = '';
+ const [variants] = await this.connection.runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
+ + ` values ( cast( x'7B' as varchar(1) )`
+ + ` , cast( x'7C' as varchar(1) )`
+ + ` , cast( x'5B' as varchar(1) ) )`
+ + `)`
+ + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+
+ if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
+ localEncoding = variants.LOCAL;
}
- async getLocalEncodingValues(): Promise {
+ return Promise.resolve(localEncoding);
- let localEncoding = '';
+ }
- const [variants] = await this.connection.runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
- + ` values ( cast( x'7B' as varchar(1) )`
- + ` , cast( x'7C' as varchar(1) )`
- + ` , cast( x'5B' as varchar(1) ) )`
- + `)`
- + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+ async setBash(): Promise {
- if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
- localEncoding = variants.LOCAL;
- }
+ let bashset = false;
- return Promise.resolve(localEncoding);
+ const commandSetBashResult = await this.connection.sendCommand({
+ command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
+ });
- }
+ if (!commandSetBashResult.stderr) bashset = true;
- async setBash(): Promise {
+ return Promise.resolve(bashset);
- let bashset = false;
+ }
- const commandSetBashResult = await this.connection.sendCommand({
- command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
- });
+ async getEnvironmentVariable(envVar: string): Promise {
+ return (await this.connection.sendCommand({ command: `echo ${envVar}` })).stdout.split(":");
+ }
- if (!commandSetBashResult.stderr) bashset = true;
+ async checkPaths(requiredPaths: string[]): Promise<{ reason: string, missingPath: string }> {
- return Promise.resolve(bashset);
+ const currentPaths = await this.getEnvironmentVariable('$PATH');
- }
+ let reason = '';
+ let missingPath = '';
- async getEnvironmentVariable(envVar: string): Promise {
- return (await this.connection.sendCommand({ command: `echo ${envVar}` })).stdout.split(":");
+ for (const requiredPath of requiredPaths) {
+ if (!currentPaths.includes(requiredPath)) {
+ reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
+ missingPath = requiredPath
+ break;
+ }
+ }
+ // If reason is still undefined, then we know the user has all the required paths. Then we don't
+ // need to check for their existence before checking the order of the required paths.
+
+ if (!reason &&
+ (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
+ || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
+ reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
+ missingPath = "/QOpenSys/pkgs/bin"
}
- async checkPaths(requiredPaths: string[]): Promise<{ reason: string, missingPath: string }> {
+ return Promise.resolve({ reason: reason, missingPath: missingPath });
- const currentPaths = await this.getEnvironmentVariable('$PATH');
+ }
- let reason = '';
- let missingPath = '';
+ async checkBashRCFile(bashrcFile: string): Promise {
- for (const requiredPath of requiredPaths) {
- if (!currentPaths.includes(requiredPath)) {
- reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
- missingPath = requiredPath
- break;
- }
- }
- // If reason is still undefined, then we know the user has all the required paths. Then we don't
- // need to check for their existence before checking the order of the required paths.
-
- if (!reason &&
- (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
- || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
- reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
- missingPath = "/QOpenSys/pkgs/bin"
- }
+ let bashrcExists = false;
- return Promise.resolve({ reason: reason, missingPath: missingPath });
+ bashrcExists = (await this.connection.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
- }
+ return Promise.resolve(bashrcExists);
+ }
- async checkBashRCFile(bashrcFile: string): Promise {
+ async createBashrcFile(bashrcFile: string, username: string): Promise<{ createBash: boolean, createBashMsg: string }> {
- let bashrcExists = false;
+ let createBash = true;
+ let createBashMsg = '';
- bashrcExists = (await this.connection.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
+ // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
+ // all the required paths, but we don't overwrite the priority of other items on their path.
+ const createBashrc = await this.connection.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
- return Promise.resolve(bashrcExists);
+ if (createBashrc.code !== 0) {
+ createBash = false;
+ createBashMsg = createBashrc.stderr;
}
- async createBashrcFile(bashrcFile: string, username: string): Promise<{ createBash: boolean, createBashMsg: string }> {
+ return Promise.resolve({ createBash, createBashMsg });
- let createBash = true;
- let createBashMsg = '';
+ }
- // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
- // all the required paths, but we don't overwrite the priority of other items on their path.
- const createBashrc = await this.connection.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
+ async updateBashrcFile(bashrcFile: string): Promise<{ updateBash: boolean, updateBashMsg: string }> {
- if (createBashrc.code !== 0) {
- createBash = false;
- createBashMsg = createBashrc.stderr;
- }
+ let updateBash = true;
+ let updateBashMsg = '';
- return Promise.resolve({ createBash, createBashMsg });
-
- }
-
- async updateBashrcFile(bashrcFile: string): Promise<{ updateBash: boolean, updateBashMsg: string }> {
-
- let updateBash = true;
- let updateBashMsg = '';
-
- try {
- const content = this.connection.content;
- if (content) {
- const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
- let replaced = false;
- bashrcContent.forEach((line, index) => {
- if (!replaced) {
- const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
- if (pathRegex) {
- bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
- .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
- .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
- replaced = true;
- }
- }
- });
-
- if (!replaced) {
- bashrcContent.push(
- "",
- "# Generated by Code for IBM i",
- "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
- );
- }
-
- await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
+ try {
+ const content = this.connection.content;
+ if (content) {
+ const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
+ let replaced = false;
+ bashrcContent.forEach((line, index) => {
+ if (!replaced) {
+ const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
+ if (pathRegex) {
+ bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
+ .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
+ .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
+ replaced = true;
}
- }
- catch (error) {
- updateBash = false;
- updateBashMsg = error;
+ }
+ });
+
+ if (!replaced) {
+ bashrcContent.push(
+ "",
+ "# Generated by Code for IBM i",
+ "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
+ );
}
- return Promise.resolve({ updateBash, updateBashMsg });
+ await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
+ }
+ }
+ catch (error) {
+ updateBash = false;
+ updateBashMsg = error;
}
- async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{ validLibs: string[], badLibs: string[] }> {
+ return Promise.resolve({ updateBash, updateBashMsg });
+ }
- let validLibs: string[] = [];
- let badLibs: string[] = [];
+ async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{ validLibs: string[], badLibs: string[] }> {
- const result = await this.connection.sendQsh({
- command: [
- `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
- ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
- ].join(`; `)
- });
+ let validLibs: string[] = [];
+ let badLibs: string[] = [];
- if (result.stderr) {
- const lines = result.stderr.split(`\n`);
+ const result = await this.connection.sendQsh({
+ command: [
+ `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
+ ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
+ ].join(`; `)
+ });
- lines.forEach(line => {
- const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
+ if (result.stderr) {
+ const lines = result.stderr.split(`\n`);
- // If there is an error about the library, store it
- if (badLib) badLibs.push(badLib);
- });
- }
+ lines.forEach(line => {
+ const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
- if (result && badLibs.length > 0) {
- validLibs = libraryList.filter(lib => !badLibs.includes(lib));
- }
-
- return Promise.resolve({ validLibs, badLibs });
+ // If there is an error about the library, store it
+ if (badLib) badLibs.push(badLib);
+ });
+ }
+ if (result && badLibs.length > 0) {
+ validLibs = libraryList.filter(lib => !badLibs.includes(lib));
}
+ return Promise.resolve({ validLibs, badLibs });
+
+ }
}
\ No newline at end of file
From 58bb714e795942cd27124e3127deefe0258ab0e8 Mon Sep 17 00:00:00 2001
From: krethan
Date: Thu, 26 Sep 2024 23:15:13 -0600
Subject: [PATCH 25/28] Seperating logic from IBMi.ts into a new class.
---
src/api/IBMi.ts | 566 ++++++++++------------------------------
src/api/IBMiApps.ts | 87 ++++++
src/api/IBMiSettings.ts | 515 ++++++++++++++++++++++++++++++++++++
src/typings.ts | 21 +-
4 files changed, 758 insertions(+), 431 deletions(-)
create mode 100644 src/api/IBMiApps.ts
create mode 100644 src/api/IBMiSettings.ts
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index f8d1d0fab..a39860ef5 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -8,7 +8,7 @@ import { IBMiComponent, IBMiComponentType } from "../components/component";
import { CopyToImport } from "../components/copyToImport";
import { ComponentManager } from "../components/manager";
import { instance } from "../instantiate";
-import { CommandData, CommandResult, ConnectionData, IBMiMember, RemoteCommand, SpecialAuthorities, WrapResult } from "../typings";
+import { CommandData, CommandResult, ConnectionData, MemberParts, RemoteCommand, RemoteFeatures, SpecialAuthorities, WrapResult } from "../typings";
import { CompileTools } from "./CompileTools";
import { ConnectionConfiguration } from "./Configuration";
import IBMiContent from "./IBMiContent";
@@ -17,38 +17,12 @@ import { Tools } from './Tools';
import * as configVars from './configVars';
import { DebugConfiguration } from "./debug/config";
import { debugPTFInstalled } from "./debug/server";
-
-export interface MemberParts extends IBMiMember {
- basename: string
-}
+import IBMiSettings from "./IBMiSettings";
+import IBMiApps from "./IBMiApps";
const CCSID_SYSVAL = -2;
const bashShellPath = '/QOpenSys/pkgs/bin/bash';
-const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures' below!!
- {
- path: `/usr/bin/`,
- names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
- },
- {
- path: `/QOpenSys/pkgs/bin/`,
- names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
- },
- {
- path: `/QSYS.LIB/`,
- // In the future, we may use a generic specific.
- // Right now we only need one program
- // specific: `*.PGM`,
- specific: `QZDFMDB2.PGM`,
- names: [`QZDFMDB2.PGM`]
- },
- {
- path: `/QIBM/ProdData/IBMiDebugService/bin/`,
- specific: `startDebugService.sh`,
- names: [`startDebugService.sh`]
- }
-];
-
export default class IBMi {
private qccsid: number = 65535;
private jobCcsid: number = CCSID_SYSVAL;
@@ -72,7 +46,7 @@ export default class IBMi {
* the root of the IFS, thus why we store it.
*/
aspInfo: { [id: number]: string } = {};
- remoteFeatures: { [name: string]: string | undefined };
+ remoteFeatures: RemoteFeatures;
variantChars: { american: string, local: string };
/**
@@ -94,30 +68,7 @@ export default class IBMi {
constructor() {
this.client = new node_ssh.NodeSSH;
- this.remoteFeatures = {
- git: undefined,
- grep: undefined,
- tn5250: undefined,
- setccsid: undefined,
- md5sum: undefined,
- bash: undefined,
- chsh: undefined,
- stat: undefined,
- sort: undefined,
- 'GETNEWLIBL.PGM': undefined,
- 'GETMBRINFO.SQL': undefined,
- 'QZDFMDB2.PGM': undefined,
- 'startDebugService.sh': undefined,
- attr: undefined,
- iconv: undefined,
- tar: undefined,
- ls: undefined,
- find: undefined,
- jdk80: undefined,
- jdk11: undefined,
- jdk17: undefined,
- openjdk11: undefined
- };
+ this.remoteFeatures = {};
this.variantChars = {
american: `#@$`,
@@ -195,17 +146,17 @@ export default class IBMi {
// Reload server settings?
const quickConnect = (this.config.quickConnect === true && reloadServerSettings === false);
+ //Initialize IBMiConnectionSettings to be used throughout to get settings
+ let connSettings = new IBMiSettings(this);
+
// Check shell output for additional user text - this will confuse Code...
progress.report({
message: `Checking shell output.`
});
- const checkShellText = `This should be the only text!`;
- const checkShellResult = await this.sendCommand({
- command: `echo "${checkShellText}"`,
- directory: `.`
- });
- if (checkShellResult.stdout.split(`\n`)[0] !== checkShellText) {
+ const checkShellResult = await connSettings.checkShellOutput();
+
+ if (!checkShellResult) {
const chosen = await vscode.window.showErrorMessage(`Error in shell configuration!`, {
detail: [
`This extension can not work with the shell configured on ${this.currentConnectionName},`,
@@ -237,74 +188,37 @@ export default class IBMi {
message: `Checking home directory.`
});
- let defaultHomeDir;
-
- const echoHomeResult = await this.sendCommand({
- command: `echo $HOME && cd && test -w $HOME`,
- directory: `.`
- });
- // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
- // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
- // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
- // if we iterate on this code again in the future)
- // - stdout contains the name of the home directory (even if it does not exist)
- // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
- // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
- let isHomeUsable = (0 == echoHomeResult.code);
- if (isHomeUsable) {
- defaultHomeDir = echoHomeResult.stdout.trim();
- } else {
- // Let's try to provide more valuable information to the user about why their home directory
- // is bad and maybe even provide the opportunity to create the home directory
-
- let actualHomeDir = echoHomeResult.stdout.trim();
-
- // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
- let doesHomeExist = (0 === (await this.sendCommand({ command: `test -e ${actualHomeDir}` })).code);
- if (doesHomeExist) {
- // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
- // But, remember, but we only got here if 'cd $HOME' failed.
- // Let's try to figure out why....
- if (0 !== (await this.sendCommand({ command: `test -d ${actualHomeDir}` })).code) {
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
- }
- else if (0 !== (await this.sendCommand({ command: `test -w ${actualHomeDir}` })).code) {
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
- }
- else if (0 !== (await this.sendCommand({ command: `test -x ${actualHomeDir}` })).code) {
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
+ const homeResult = await connSettings.getHomeDirectory();
+ if (homeResult.homeMsg) {
+ if (homeResult.homeExists) {
+ //Home Directory exists but give informational message
+ await vscode.window.showWarningMessage(homeResult.homeMsg, { modal: !reconnecting });
+ }
+ else {
+ //Home Directory does not exist
+ if (reconnecting) {
+ vscode.window.showWarningMessage(homeResult.homeMsg, { modal: false });
}
else {
- // not sure, but get your sys admin involved
- await vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: !reconnecting });
- }
- }
- else if (reconnecting) {
- vscode.window.showWarningMessage(`Your home directory (${actualHomeDir}) does not exist. Code for IBM i may not function correctly.`, { modal: false });
- }
- else if (await vscode.window.showWarningMessage(`Home directory does not exist`, {
- modal: true,
- detail: `Your home directory (${actualHomeDir}) does not exist, so Code for IBM i may not function correctly. Would you like to create this directory now?`,
- }, `Yes`)) {
- this.appendOutput(`creating home directory ${actualHomeDir}`);
- let mkHomeCmd = `mkdir -p ${actualHomeDir} && chown ${connectionObject.username.toLowerCase()} ${actualHomeDir} && chmod 0755 ${actualHomeDir}`;
- let mkHomeResult = await this.sendCommand({ command: mkHomeCmd, directory: `.` });
- if (0 === mkHomeResult.code) {
- defaultHomeDir = actualHomeDir;
- } else {
- let mkHomeErrs = mkHomeResult.stderr;
- // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
- mkHomeErrs = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
- await vscode.window.showWarningMessage(`Error creating home directory (${actualHomeDir}):\n${mkHomeErrs}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
+ if (await vscode.window.showWarningMessage(`Home directory does not exist`, {
+ modal: true,
+ detail: `Your home directory (${homeResult.homeDir}) does not exist, so Code for IBM i may not function correctly. Would you like to create this directory now?`,
+ }, `Yes`)) {
+ this.appendOutput(`creating home directory ${homeResult.homeDir}`);
+ let homeCreatedResult = await connSettings.createHomeDirectory(homeResult.homeDir, connectionObject.username);
+ if (!homeCreatedResult.homeCreated) {
+ await vscode.window.showWarningMessage(homeCreatedResult.homeMsg, { modal: true });
+ }
+ }
}
}
}
// Check to see if we need to store a new value for the home directory
- if (defaultHomeDir) {
- if (this.config.homeDirectory !== defaultHomeDir) {
- this.config.homeDirectory = defaultHomeDir;
- vscode.window.showInformationMessage(`Configured home directory reset to ${defaultHomeDir}.`);
+ if (homeResult.homeDir) {
+ if (this.config.homeDirectory !== homeResult.homeDir) {
+ this.config.homeDirectory = homeResult.homeDir;
+ vscode.window.showInformationMessage(`Configured home directory reset to ${homeResult.homeDir}.`);
}
} else {
// New connections always have `.` as the initial value.
@@ -315,7 +229,7 @@ export default class IBMi {
//Set a default IFS listing
if (this.config.ifsShortcuts.length === 0) {
- if (defaultHomeDir) {
+ if (homeResult.homeDir) {
this.config.ifsShortcuts = [this.config.homeDirectory];
} else {
this.config.ifsShortcuts = [`/`];
@@ -326,42 +240,19 @@ export default class IBMi {
message: `Checking library list configuration.`
});
- //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
- //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
- let currentLibrary = `QGPL`;
this.defaultUserLibraries = [];
- const liblResult = await this.sendQsh({
- command: `liblist`
- });
- if (liblResult.code === 0) {
- const libraryListString = liblResult.stdout;
- if (libraryListString !== ``) {
- const libraryList = libraryListString.split(`\n`);
-
- let lib, type;
- for (const line of libraryList) {
- lib = line.substring(0, 10).trim();
- type = line.substring(12);
-
- switch (type) {
- case `USR`:
- this.defaultUserLibraries.push(lib);
- break;
+ let libraryListResult = await connSettings.getLibraryList();
+ if (libraryListResult.libStatus) {
+
+ this.defaultUserLibraries = libraryListResult.defaultUserLibraries;
- case `CUR`:
- currentLibrary = lib;
- break;
- }
- }
-
- //If this is the first time the config is made, then these arrays will be empty
- if (this.config.currentLibrary.length === 0) {
- this.config.currentLibrary = currentLibrary;
- }
- if (this.config.libraryList.length === 0) {
- this.config.libraryList = this.defaultUserLibraries;
- }
+ //If this is the first time the config is made, then these arrays will be empty
+ if (this.config.currentLibrary.length === 0) {
+ this.config.currentLibrary = libraryListResult.currentLibrary;
+ }
+ if (this.config.libraryList.length === 0) {
+ this.config.libraryList = libraryListResult.defaultUserLibraries;
}
}
@@ -370,60 +261,20 @@ export default class IBMi {
});
//Next, we need to check the temp lib (where temp outfile data lives) exists
- const createdTempLib = await this.runCommand({
- command: `CRTLIB LIB(${this.config.tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
- noLibList: true
- });
-
- if (createdTempLib.code === 0) {
- tempLibrarySet = true;
- } else {
- const messages = Tools.parseMessages(createdTempLib.stderr);
- if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
+ tempLibrarySet = await connSettings.setTempLibrary(this.config.tempLibrary);
+ if (!tempLibrarySet) {
+ if (libraryListResult.currentLibrary && !libraryListResult.currentLibrary.startsWith(`Q`)) {
+ //Using ${currentLibrary} as the temporary library for temporary data.
+ this.config.tempLibrary = this.config.currentLibrary;
tempLibrarySet = true;
}
- else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
- const tempLibExists = await this.runCommand({
- command: `CHKOBJ OBJ(QSYS/${this.config.tempLibrary}) OBJTYPE(*LIB)`,
- noLibList: true
- });
-
- if (tempLibExists.code === 0) {
- //We're all good if no errors
- tempLibrarySet = true;
- } else if (currentLibrary && !currentLibrary.startsWith(`Q`)) {
- //Using ${currentLibrary} as the temporary library for temporary data.
- this.config.tempLibrary = currentLibrary;
- tempLibrarySet = true;
- }
- }
}
progress.report({
message: `Checking temporary directory configuration.`
});
- let tempDirSet = false;
- // Next, we need to check if the temp directory exists
- let result = await this.sendCommand({
- command: `[ -d "${this.config.tempDir}" ]`
- });
-
- if (result.code === 0) {
- // Directory exists
- tempDirSet = true;
- } else {
- // Directory does not exist, try to create it
- let result = await this.sendCommand({
- command: `mkdir -p ${this.config.tempDir}`
- });
- if (result.code === 0) {
- // Directory created
- tempDirSet = true;
- } else {
- // Directory not created
- }
- }
+ let tempDirSet = await connSettings.setTempDirectory(this.config.tempDir);
if (!tempDirSet) {
this.config.tempDir = `/tmp`;
@@ -433,47 +284,38 @@ export default class IBMi {
progress.report({
message: `Clearing temporary data.`
});
-
- this.runCommand({
- command: `DLTOBJ OBJ(${this.config.tempLibrary}/O_*) OBJTYPE(*FILE)`,
- noLibList: true,
- })
- .then(result => {
- // All good!
- if (result && result.stderr) {
- const messages = Tools.parseMessages(result.stderr);
- if (!messages.findId(`CPF2125`)) {
- // @ts-ignore We know the config exists.
- vscode.window.showErrorMessage(`Temporary data not cleared from ${this.config.tempLibrary}.`, `View log`).then(async choice => {
- if (choice === `View log`) {
- this.outputChannel!.show();
- }
- });
+ //Clear Temporary Library Data
+ let clearMsg = await connSettings.clearTempLibrary(this.config.tempLibrary);
+ if (clearMsg) {
+ // @ts-ignore We know the config exists.
+ vscode.window.showErrorMessage(clearMsg, `View log`).then
+ (async choice => {
+ if (choice === `View log`) {
+ this.outputChannel!.show();
}
- }
- })
-
- this.sendCommand({
- command: `rm -rf ${path.posix.join(this.config.tempDir, `vscodetemp*`)}`
- })
- .then(result => {
- // All good!
- })
- .catch(e => {
- // CPF2125: No objects deleted.
- // @ts-ignore We know the config exists.
- vscode.window.showErrorMessage(`Temporary data not cleared from ${this.config.tempDir}.`, `View log`).then(async choice => {
+ });
+ }
+
+ //Clear Temporary Directory Data
+ clearMsg = await connSettings.clearTempDirectory(this.config.tempDir);
+ if (clearMsg) {
+ // @ts-ignore We know the config exists.
+ vscode.window.showErrorMessage(clearMsg, `View log`).then
+ (async choice => {
if (choice === `View log`) {
this.outputChannel!.show();
}
});
- });
+ }
+
}
+ //TO DO: why is this required????
const commandShellResult = await this.sendCommand({
command: `echo $SHELL`
});
+ //TO DO: why is this required????
if (commandShellResult.code === 0) {
this.shell = commandShellResult.stdout.trim();
}
@@ -486,29 +328,26 @@ export default class IBMi {
message: `Checking for bad data areas.`
});
- const QCPTOIMPF = await this.runCommand({
- command: `CHKOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- });
+ const QCPTOIMPF = await connSettings.checkObjectExists('QSYS', 'QCPTOIMPF', '*DTAARA');
- if (QCPTOIMPF?.code === 0) {
+ if (QCPTOIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPTOIMPF exists on this system and may impact Code for IBM i functionality.`, {
detail: `For V5R3, the code for the command CPYTOIMPF had a major design change to increase functionality and performance. The QSYS/QCPTOIMPF data area lets developers keep the pre-V5R2 version of CPYTOIMPF. Code for IBM i cannot function correctly while this data area exists.`,
modal: true,
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- this.runCommand({
- command: `DLTOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- })
- .then((result) => {
- if (result?.code === 0) {
- vscode.window.showInformationMessage(`The data area QSYS/QCPTOIMPF has been deleted.`);
- } else {
- vscode.window.showInformationMessage(`Failed to delete the data area QSYS/QCPTOIMPF. Code for IBM i may not work as intended.`);
- }
- })
+ connSettings.deleteObject('QSYS', 'QCPTOIMPF', '*DTAARA').then((result) => {
+ if (result) {
+ vscode.window.showInformationMessage(`The data
+ area QSYS/QCPTOIMPF has been deleted.`);
+ }
+ else {
+ vscode.window.showInformationMessage(`Failed to
+ delete the data area QSYS/QCPTOIMPF. Code for IBM
+ i may not work as intended.`);
+ }
+ });
break;
case `Read more`:
vscode.env.openExternal(vscode.Uri.parse(`https://github.com/codefori/vscode-ibmi/issues/476#issuecomment-1018908018`));
@@ -517,23 +356,17 @@ export default class IBMi {
});
}
- const QCPFRMIMPF = await this.runCommand({
- command: `CHKOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- });
+ const QCPFRMIMPF = await connSettings.checkObjectExists('QSYS', 'QCPFRMIMPF', '*DTAARA');
- if (QCPFRMIMPF?.code === 0) {
+ if (QCPFRMIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPFRMIMPF exists on this system and may impact Code for IBM i functionality.`, {
modal: false,
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- this.runCommand({
- command: `DLTOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`,
- noLibList: true
- })
+ connSettings.deleteObject('QSYS', 'QCPFRMIMPF', '*DTAARA')
.then((result) => {
- if (result?.code === 0) {
+ if (result) {
vscode.window.showInformationMessage(`The data area QSYS/QCPFRMIMPF has been deleted.`);
} else {
vscode.window.showInformationMessage(`Failed to delete the data area QSYS/QCPFRMIMPF. Code for IBM i may not work as intended.`);
@@ -557,8 +390,10 @@ export default class IBMi {
message: `Checking installed components on host IBM i.`
});
+ let remoteApps = new IBMiApps();
+
// We need to check if our remote programs are installed.
- remoteApps.push(
+ remoteApps.addRemoteApp(
{
path: `/QSYS.lib/${this.upperCaseName(this.config.tempLibrary)}.lib/`,
names: [`GETNEWLIBL.PGM`],
@@ -568,59 +403,23 @@ export default class IBMi {
//Next, we see what pase features are available (installed via yum)
//This may enable certain features in the future.
- for (const feature of remoteApps) {
+ for (const remoteApp of remoteApps.getRemoteApps()) {
try {
progress.report({
- message: `Checking installed components on host IBM i: ${feature.path}`
+ message: `Checking installed components on host IBM i: ${remoteApp.path}`
});
- const call = await this.sendCommand({ command: `ls -p ${feature.path}${feature.specific || ``}` });
- if (call.stdout) {
- const files = call.stdout.split(`\n`);
-
- if (feature.specific) {
- for (const name of feature.names)
- this.remoteFeatures[name] = files.find(file => file.includes(name));
- } else {
- for (const name of feature.names)
- if (files.includes(name))
- this.remoteFeatures[name] = feature.path + name;
- }
- }
+ await remoteApps.checkRemoteFeatures(remoteApp, this);
+ this.remoteFeatures = remoteApps.getRemoteFeatures();
+
} catch (e) {
console.log(e);
}
}
-
- //Specific Java installations check
- progress.report({
- message: `Checking installed components on host IBM i: Java`
- });
- const javaCheck = async (root: string) => await this.content.testStreamFile(`${root}/bin/java`, 'x') ? root : undefined;
- this.remoteFeatures.jdk80 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`);
- this.remoteFeatures.jdk11 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit`);
- this.remoteFeatures.openjdk11 = await javaCheck(`/QOpensys/pkgs/lib/jvm/openjdk-11`);
- this.remoteFeatures.jdk17 = await javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk17/64bit`);
}
if (this.sqlRunnerAvailable()) {
- //Temporary function to run SQL
-
- // TODO: stop using this runSQL function and this.runSql
- const runSQL = async (statement: string) => {
- const output = await this.sendCommand({
- command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`,
- stdin: statement
- });
-
- if (output.code === 0) {
- return Tools.db2Parse(output.stdout);
- }
- else {
- throw new Error(output.stdout);
- }
- };
-
+
// Check for ASP information?
if (quickConnect === true && cachedServerSettings?.aspInfo) {
this.aspInfo = cachedServerSettings.aspInfo;
@@ -631,19 +430,14 @@ export default class IBMi {
//This is mostly a nice to have. We grab the ASP info so user's do
//not have to provide the ASP in the settings.
- try {
- const resultSet = await runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
- resultSet.forEach(row => {
- if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
- this.aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
- }
- });
- } catch (e) {
- //Oh well
+
+ this.aspInfo = await connSettings.getASPInfo();
+ if (Object.keys(this.aspInfo).length === 0) {
progress.report({
message: `Failed to get ASP information.`
});
}
+
}
// Fetch conversion values?
@@ -660,18 +454,11 @@ export default class IBMi {
// Next, we're going to see if we can get the CCSID from the user or the system.
// Some things don't work without it!!!
try {
-
// we need to grab the system CCSID (QCCSID)
- const [systemCCSID] = await runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
- if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
- this.qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
- }
+ this.qccsid = await connSettings.getQCCSID();
// we grab the users default CCSID
- const [userInfo] = await runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`);
- if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
- this.jobCcsid = userInfo.CHARACTER_CODE_SET_ID;
- }
+ this.jobCcsid = await connSettings.getjobCCSID(this.currentUser);
// if the job ccsid is *SYSVAL, then assign it to sysval
if (this.jobCcsid === CCSID_SYSVAL) {
@@ -679,36 +466,14 @@ export default class IBMi {
}
// Let's also get the user's default CCSID
- try {
- const [activeJob] = await runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
- this.userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
- }
- catch (error) {
- const [defaultCCSID] = (await this.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
- .stdout
- .split("\n")
- .filter(line => line.includes("DFTCCSID"));
-
- const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
- if (defaultCCSCID && !isNaN(defaultCCSCID)) {
- this.userDefaultCCSID = defaultCCSCID;
- }
- }
+ this.userDefaultCCSID = await connSettings.getDefaultCCSID();
progress.report({
message: `Fetching local encoding values.`
});
- const [variants] = await runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
- + ` values ( cast( x'7B' as varchar(1) )`
- + ` , cast( x'7C' as varchar(1) )`
- + ` , cast( x'5B' as varchar(1) ) )`
- + `)`
- + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+ this.variantChars.local = await connSettings.getLocalEncodingValues();
- if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
- this.variantChars.local = variants.LOCAL;
- }
} catch (e) {
// Oh well!
console.log(e);
@@ -751,11 +516,10 @@ export default class IBMi {
vscode.window.showInformationMessage(`IBM recommends using bash as your default shell.`, `Set shell to bash`, `Read More`,).then(async choice => {
switch (choice) {
case `Set shell to bash`:
- const commandSetBashResult = await this.sendCommand({
- command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
- });
- if (!commandSetBashResult.stderr) {
+ const commandSetBashResult = await connSettings.setBash();
+
+ if (!commandSetBashResult) {
vscode.window.showInformationMessage(`Shell is now bash! Reconnect for change to take effect.`);
usesBash = true;
} else {
@@ -778,72 +542,32 @@ export default class IBMi {
});
if ((!quickConnect || !cachedServerSettings?.pathChecked)) {
- const currentPaths = (await this.sendCommand({ command: "echo $PATH" })).stdout.split(":");
- const bashrcFile = `${defaultHomeDir}/.bashrc`;
- let bashrcExists = (await this.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
- let reason;
- const requiredPaths = ["/QOpenSys/pkgs/bin", "/usr/bin", "/QOpenSys/usr/bin"]
- let missingPath;
- for (const requiredPath of requiredPaths) {
- if (!currentPaths.includes(requiredPath)) {
- reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
- missingPath = requiredPath
- break;
- }
- }
- // If reason is still undefined, then we know the user has all the required paths. Then we don't
- // need to check for their existence before checking the order of the required paths.
- if (!reason &&
- (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
- || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
- reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
- missingPath = "/QOpenSys/pkgs/bin"
- }
- if (reason && await vscode.window.showWarningMessage(`${missingPath} not found in $PATH`, {
+
+ const bashrcFile = `${homeResult.homeDir}/.bashrc`;
+
+ let bashrcExists = await connSettings.checkBashRCFile(bashrcFile);
+
+ let checkPathResult = await connSettings.checkPaths(["/QOpenSys/pkgs/bin", "/usr/bin", "/QOpenSys/usr/bin"]);
+
+ if (checkPathResult.reason && await vscode.window.showWarningMessage(`${checkPathResult.missingPath} not found in $PATH`, {
modal: true,
- detail: `${reason}, so Code for IBM i may not function correctly. Would you like to ${bashrcExists ? "update" : "create"} ${bashrcFile} to fix this now?`,
+ detail: `${checkPathResult.reason}, so Code for IBM i may not function correctly. Would you like to ${bashrcExists ? "update" : "create"} ${bashrcFile} to fix this now?`,
}, `Yes`)) {
delayedOperations.push(async () => {
this.appendOutput(`${bashrcExists ? "update" : "create"} ${bashrcFile}`);
if (!bashrcExists) {
- // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
- // all the required paths, but we don't overwrite the priority of other items on their path.
- const createBashrc = await this.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${connectionObject.username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
- if (createBashrc.code !== 0) {
- vscode.window.showWarningMessage(`Error creating ${bashrcFile}):\n${createBashrc.stderr}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
+ //Create bashrc File
+ let createBashResult = await connSettings.createBashrcFile(bashrcFile,connectionObject.username);
+ //Error creating bashrc File
+ if (!createBashResult.createBash) {
+ vscode.window.showWarningMessage(`Error creating ${bashrcFile}):\n${createBashResult.createBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
}
}
else {
- try {
- const content = this.content;
- if (content) {
- const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
- let replaced = false;
- bashrcContent.forEach((line, index) => {
- if (!replaced) {
- const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
- if (pathRegex) {
- bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
- .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
- .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
- replaced = true;
- }
- }
- });
-
- if (!replaced) {
- bashrcContent.push(
- "",
- "# Generated by Code for IBM i",
- "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
- );
- }
-
- await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
- }
- }
- catch (error) {
- vscode.window.showWarningMessage(`Error modifying PATH in ${bashrcFile}):\n${error}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
+ //Update bashRC file
+ let updateBashResult = await connSettings.updateBashrcFile(bashrcFile);
+ if(!updateBashResult.updateBash) {
+ vscode.window.showWarningMessage(`Error modifying PATH in ${bashrcFile}):\n${updateBashResult.updateBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
}
}
});
@@ -864,7 +588,7 @@ export default class IBMi {
}
}
- if (defaultHomeDir) {
+ if (homeResult.homeDir) {
if (!tempLibrarySet) {
vscode.window.showWarningMessage(`Code for IBM i will not function correctly until the temporary library has been corrected in the settings.`, `Open Settings`)
.then(result => {
@@ -887,34 +611,14 @@ export default class IBMi {
progress.report({
message: `Validate configured library list`
});
- let validLibs: string[] = [];
- let badLibs: string[] = [];
-
- const result = await this.sendQsh({
- command: [
- `liblist -d ` + this.defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
- ...this.config.libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
- ].join(`; `)
- });
- if (result.stderr) {
- const lines = result.stderr.split(`\n`);
-
- lines.forEach(line => {
- const badLib = this.config?.libraryList.find(lib => line.includes(`ibrary ${lib} `));
-
- // If there is an error about the library, store it
- if (badLib) badLibs.push(badLib);
- });
- }
-
- if (result && badLibs.length > 0) {
- validLibs = this.config.libraryList.filter(lib => !badLibs.includes(lib));
- const chosen = await vscode.window.showWarningMessage(`The following ${badLibs.length > 1 ? `libraries` : `library`} does not exist: ${badLibs.join(`,`)}. Remove ${badLibs.length > 1 ? `them` : `it`} from the library list?`, `Yes`, `No`);
+ let libraryListResult = await connSettings.validateLibraryList(this.defaultUserLibraries,this.config.libraryList);
+ if(libraryListResult.badLibs.length > 0) {
+ const chosen = await vscode.window.showWarningMessage(`The following ${libraryListResult.badLibs.length > 1 ? `libraries` : `library`} does not exist: ${libraryListResult.badLibs.join(`,`)}. Remove ${libraryListResult.badLibs.length > 1 ? `them` : `it`} from the library list?`, `Yes`, `No`);
if (chosen === `Yes`) {
- this.config!.libraryList = validLibs;
+ this.config!.libraryList = libraryListResult.validLibs;
} else {
- vscode.window.showWarningMessage(`The following libraries does not exist: ${badLibs.join(`,`)}.`);
+ vscode.window.showWarningMessage(`The following libraries does not exist: ${libraryListResult.badLibs.join(`,`)}.`);
}
}
}
@@ -1379,6 +1083,8 @@ export default class IBMi {
* @param statements
* @returns a Result set
*/
+
+ // TODO: stop using this.runSql
async runSQL(statements: string): Promise {
const { 'QZDFMDB2.PGM': QZDFMDB2 } = this.remoteFeatures;
diff --git a/src/api/IBMiApps.ts b/src/api/IBMiApps.ts
new file mode 100644
index 000000000..abfc48974
--- /dev/null
+++ b/src/api/IBMiApps.ts
@@ -0,0 +1,87 @@
+import IBMi from "./IBMi";
+import { RemoteApp, RemoteApps, RemoteFeatures } from "../typings";
+
+export default class IBMiApps {
+
+ private remoteApps: RemoteApps;
+ private remoteFeatures: RemoteFeatures;
+
+ constructor() {
+ this.remoteApps = [
+ {
+ path: `/usr/bin/`,
+ names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
+ },
+ {
+ path: `/QOpenSys/pkgs/bin/`,
+ names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
+ },
+ {
+ path: `/QSYS.LIB/`,
+ // In the future, we may use a generic specific.
+ // Right now we only need one program
+ // specific: `*.PGM`,
+ specific: `QZDFMDB2.PGM`,
+ names: [`QZDFMDB2.PGM`]
+ },
+ {
+ path: `/QIBM/ProdData/IBMiDebugService/bin/`,
+ specific: `startDebugService.sh`,
+ names: [`startDebugService.sh`]
+ }
+ ];
+
+ this.remoteFeatures = {};
+ this.setRemoteFeatures();
+
+ }
+
+ addRemoteApp(remoteApp: RemoteApp) {
+
+ //Add remote App
+ this.remoteApps.push(remoteApp);
+
+ //Add possible features to list
+ for(const name of remoteApp.names) {
+ this.remoteFeatures[name] = undefined;
+ }
+
+ }
+
+ getRemoteApps(): RemoteApps {
+ return this.remoteApps;
+ }
+
+ setRemoteFeatures() {
+
+ for (const feature of this.remoteApps) {
+ for(const name of feature.names) {
+ this.remoteFeatures[name] = undefined;
+ }
+ }
+
+ }
+
+ getRemoteFeatures(): RemoteFeatures {
+ return this.remoteFeatures;
+ }
+
+ async checkRemoteFeatures(remoteApp: RemoteApp, connection: IBMi) {
+
+ const call = await connection.sendCommand({ command: `ls -p ${remoteApp.path}${remoteApp.specific || ``}` });
+ if (call.stdout) {
+ const files = call.stdout.split(`\n`);
+
+ if (remoteApp.specific) {
+ for (const name of remoteApp.names)
+ this.remoteFeatures[name] = files.find(file => file.includes(name));
+ } else {
+ for (const name of remoteApp.names)
+ if (files.includes(name))
+ this.remoteFeatures[name] = remoteApp.path + name;
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/api/IBMiSettings.ts b/src/api/IBMiSettings.ts
new file mode 100644
index 000000000..ad5d07869
--- /dev/null
+++ b/src/api/IBMiSettings.ts
@@ -0,0 +1,515 @@
+import IBMi from "./IBMi";
+import { Tools } from "./Tools";
+import path from 'path';
+import { aspInfo } from "../typings";
+
+const CCSID_SYSVAL = -2;
+
+export default class IBMiSettings {
+
+ constructor(private connection: IBMi) {
+
+ }
+
+ async checkShellOutput(): Promise {
+
+ const checkShellText = `This should be the only text!`;
+ const checkShellResult = await this.connection.sendCommand({
+ command: `echo "${checkShellText}"`,
+ directory: `.`
+ });
+
+ return Promise.resolve(checkShellResult.stdout.split(`\n`)[0] == checkShellText);
+
+ }
+
+ async getHomeDirectory(): Promise<{ homeExists: boolean, homeDir: string, homeMsg: string }> {
+
+ let homeDir;
+ let homeMsg = '';
+ let homeExists;
+
+ const homeResult = await this.connection.sendCommand({
+ command: `echo $HOME && cd && test -w $HOME`,
+ directory: `.`
+ });
+
+ // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
+ // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
+ // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
+ // if we iterate on this code again in the future)
+ // - stdout contains the name of the home directory (even if it does not exist)
+ // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
+ // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
+
+ homeExists = homeResult.code == 0;
+ homeDir = homeResult.stdout.trim();
+
+ if (!homeExists) {
+ // Let's try to provide more valuable information to the user about why their home directory
+ // is bad and maybe even provide the opportunity to create the home directory
+
+ // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
+ homeExists = (0 === (await this.connection.sendCommand({ command: `test -e ${homeDir}` })).code);
+ if (homeExists) {
+ // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
+ // But, remember, but we only got here if 'cd $HOME' failed.
+ // Let's try to figure out why....
+ if (0 !== (await this.connection.sendCommand({ command: `test -d ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -w ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`;
+
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -x ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else {
+ // not sure, but get your sys admin involved
+ homeMsg = `Your home directory (${homeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ }
+ else {
+ homeMsg = `Your home directory (${homeDir}) does not exist, so Code for IBM i may not function correctly.`;
+ }
+ }
+
+ return Promise.resolve({ homeExists, homeDir, homeMsg });
+
+ }
+
+ async createHomeDirectory(homeDir: string, username: string): Promise<{ homeCreated: boolean, homeMsg: string }> {
+
+ let homeCreated = false;
+ let homeMsg = '';
+
+ const homeCmd = `mkdir -p ${homeDir} && chown ${username.toLowerCase()} ${homeDir} && chmod 0755 ${homeDir}`;
+
+ let mkHomeResult = await this.connection.sendCommand({ command: homeCmd, directory: `.` });
+
+ if (0 === mkHomeResult.code) {
+ homeCreated = true;
+ } else {
+ let mkHomeErrs = mkHomeResult.stderr;
+ // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
+ homeMsg = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
+ }
+
+ return Promise.resolve({ homeCreated, homeMsg });
+
+ }
+
+ async getLibraryList(): Promise<{ libStatus: boolean, currentLibrary: string, defaultUserLibraries: string[] }> {
+
+
+
+ //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
+ //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
+
+ let currentLibrary = `QGPL`;
+ let defaultUserLibraries = [];
+ let libStatus = false;
+
+ const liblResult = await this.connection.sendQsh({
+ command: `liblist`
+ });
+
+ if (liblResult.code === 0) {
+ libStatus = true;
+ const libraryListString = liblResult.stdout;
+ if (libraryListString !== ``) {
+ const libraryList = libraryListString.split(`\n`);
+
+ let lib, type;
+ for (const line of libraryList) {
+ lib = line.substring(0, 10).trim();
+ type = line.substring(12);
+
+ switch (type) {
+ case `USR`:
+ defaultUserLibraries.push(lib);
+ break;
+
+ case `CUR`:
+ currentLibrary = lib;
+ break;
+ }
+ }
+ }
+ }
+
+ return Promise.resolve({ libStatus, currentLibrary, defaultUserLibraries });
+
+ }
+
+ async setTempLibrary(tempLibrary: string): Promise {
+
+ let tempLibrarySet = false;
+
+ //Check the temp lib (where temp outfile data lives) exists
+ const createdTempLib = await this.connection.runCommand({
+ command: `CRTLIB LIB(${tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
+ noLibList: true
+ });
+
+ if (createdTempLib.code === 0) {
+ tempLibrarySet = true;
+ }
+ else {
+ const messages = Tools.parseMessages(createdTempLib.stderr);
+ if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
+ tempLibrarySet = true;
+ }
+ else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
+ const tempLibExists = await this.connection.runCommand({
+ command: `CHKOBJ OBJ(QSYS/${tempLibrary}) OBJTYPE(*LIB)`,
+ noLibList: true
+ });
+
+ if (tempLibExists.code === 0) {
+ //We're all good if no errors
+ tempLibrarySet = true;
+ }
+ else {
+ tempLibrarySet = false;
+ }
+
+ }
+ }
+
+ return Promise.resolve(tempLibrarySet);
+
+ }
+
+ async setTempDirectory(tempDir: string): Promise {
+
+ let tempDirSet = false;
+
+ // Check if the temp directory exists
+ let result = await this.connection.sendCommand({
+ command: `[ -d "${tempDir}" ]`
+ });
+
+ if (result.code === 0) {
+ // Directory exists
+ tempDirSet = true;
+ } else {
+ // Directory does not exist, try to create it
+ let result = await this.connection.sendCommand({
+ command: `mkdir -p ${tempDir}`
+ });
+ if (result.code === 0) {
+ // Directory created
+ tempDirSet = true;
+ } else {
+ // Directory not created
+ }
+ }
+
+ return Promise.resolve(tempDirSet);
+
+ }
+
+ async clearTempLibrary(tempLibrary: string): Promise {
+
+ let clearMsg = '';
+
+ this.connection.runCommand({
+ command: `DLTOBJ OBJ(${tempLibrary}/O_*) OBJTYPE(*FILE)`,
+ noLibList: true,
+ })
+ .then(result => {
+ // All good!
+ if (result && result.stderr) {
+ const messages = Tools.parseMessages(result.stderr);
+ if (!messages.findId(`CPF2125`)) {
+ clearMsg = `Temporary data not cleared from ${tempLibrary}.`;
+ }
+ }
+ });
+
+ return Promise.resolve(clearMsg);
+
+ }
+
+ async clearTempDirectory(tempDir: string): Promise {
+
+ let clearMsg = '';
+
+ this.connection.sendCommand({
+ command: `rm -rf ${path.posix.join(tempDir, `vscodetemp*`)}`
+ })
+ .then(result => {
+ // All good!
+ })
+ .catch(e => {
+ // CPF2125: No objects deleted.
+ clearMsg = `Temporary data not cleared from ${tempDir}.`;
+ });
+
+ return Promise.resolve(clearMsg);
+
+ }
+
+ async checkObjectExists(library: string, object: string, type: string): Promise {
+
+ const objResult = await this.connection.runCommand({
+ command: `CHKOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
+ noLibList: true
+ });
+
+ return Promise.resolve(objResult.code === 0);
+
+ }
+
+ async deleteObject(library: string, object: string, type: string): Promise {
+
+ const deleteResult = await this.connection.runCommand({
+ command: `DLTOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
+ noLibList: true
+ });
+
+ return Promise.resolve(deleteResult.code === 0);
+
+ }
+
+ async getASPInfo(): Promise {
+
+ let aspInfo: aspInfo = {};
+
+ try {
+ const resultSet = await this.connection.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
+ if (resultSet.length) {
+ resultSet.forEach(row => {
+ if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
+ aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
+ }
+ });
+ }
+ } catch (e) {
+ //Oh well
+ aspInfo = {};
+ }
+
+ return Promise.resolve(aspInfo);
+
+ }
+
+ async getQCCSID(): Promise {
+
+ let qccsid = 0;
+
+ const [systemCCSID] = await this.connection.runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
+ if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
+ qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
+ }
+
+ return Promise.resolve(qccsid);
+
+ }
+
+ async getjobCCSID(userName: string): Promise {
+
+ let jobCCSID = CCSID_SYSVAL;
+
+ const [userInfo] = await this.connection.runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${userName}') ) )`);
+ if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
+ jobCCSID = userInfo.CHARACTER_CODE_SET_ID;
+ }
+
+ return Promise.resolve(jobCCSID);
+
+ }
+
+ async getDefaultCCSID(): Promise {
+
+ let userDefaultCCSID = 0;
+
+ try {
+ const [activeJob] = await this.connection.runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
+ userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
+ }
+ catch (error) {
+ const [defaultCCSID] = (await this.connection.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
+ .stdout
+ .split("\n")
+ .filter(line => line.includes("DFTCCSID"));
+
+ const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
+ if (defaultCCSCID && !isNaN(defaultCCSCID)) {
+ userDefaultCCSID = defaultCCSCID;
+ }
+ }
+
+ return Promise.resolve(userDefaultCCSID);
+
+ }
+
+ async getLocalEncodingValues(): Promise {
+
+ let localEncoding = '';
+
+ const [variants] = await this.connection.runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
+ + ` values ( cast( x'7B' as varchar(1) )`
+ + ` , cast( x'7C' as varchar(1) )`
+ + ` , cast( x'5B' as varchar(1) ) )`
+ + `)`
+ + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+
+ if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
+ localEncoding = variants.LOCAL;
+ }
+
+ return Promise.resolve(localEncoding);
+
+ }
+
+ async setBash(): Promise {
+
+ let bashset = false;
+
+ const commandSetBashResult = await this.connection.sendCommand({
+ command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
+ });
+
+ if (!commandSetBashResult.stderr) bashset = true;
+
+ return Promise.resolve(bashset);
+
+ }
+
+ async getEnvironmentVariable(envVar: string): Promise {
+ return (await this.connection.sendCommand({ command: `echo ${envVar}` })).stdout.split(":");
+ }
+
+ async checkPaths(requiredPaths: string[]): Promise<{ reason: string, missingPath: string }> {
+
+ const currentPaths = await this.getEnvironmentVariable('$PATH');
+
+ let reason = '';
+ let missingPath = '';
+
+ for (const requiredPath of requiredPaths) {
+ if (!currentPaths.includes(requiredPath)) {
+ reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
+ missingPath = requiredPath
+ break;
+ }
+ }
+ // If reason is still undefined, then we know the user has all the required paths. Then we don't
+ // need to check for their existence before checking the order of the required paths.
+ if (!reason) {
+ for (let x = 1; x <= requiredPaths.length; x++) {
+ if (currentPaths.indexOf(requiredPaths[0]) > currentPaths.indexOf(requiredPaths[x])) {
+ reason = `${requiredPaths[0]} is not in the right position in your $PATH shell environment variable`;
+ missingPath = requiredPaths[0];
+ break;
+ }
+ }
+ }
+
+ return Promise.resolve({ reason: reason, missingPath: missingPath });
+
+ }
+
+ async checkBashRCFile(bashrcFile: string): Promise {
+
+ let bashrcExists = false;
+
+ bashrcExists = (await this.connection.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
+
+ return Promise.resolve(bashrcExists);
+ }
+
+ async createBashrcFile(bashrcFile: string, username: string): Promise<{ createBash: boolean, createBashMsg: string }> {
+
+ let createBash = true;
+ let createBashMsg = '';
+
+ // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
+ // all the required paths, but we don't overwrite the priority of other items on their path.
+ const createBashrc = await this.connection.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
+
+ if (createBashrc.code !== 0) {
+ createBash = false;
+ createBashMsg = createBashrc.stderr;
+ }
+
+ return Promise.resolve({ createBash, createBashMsg });
+
+ }
+
+ async updateBashrcFile(bashrcFile: string): Promise<{ updateBash: boolean, updateBashMsg: string }> {
+
+ let updateBash = true;
+ let updateBashMsg = '';
+
+ try {
+ const content = this.connection.content;
+ if (content) {
+ const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
+ let replaced = false;
+ bashrcContent.forEach((line, index) => {
+ if (!replaced) {
+ const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
+ if (pathRegex) {
+ bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
+ .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
+ .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
+ replaced = true;
+ }
+ }
+ });
+
+ if (!replaced) {
+ bashrcContent.push(
+ "",
+ "# Generated by Code for IBM i",
+ "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
+ );
+ }
+
+ await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
+ }
+ }
+ catch (error) {
+ updateBash = false;
+ updateBashMsg = error;
+ }
+
+ return Promise.resolve({ updateBash, updateBashMsg });
+ }
+
+ async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{validLibs: string[], badLibs: string[]}> {
+
+ let validLibs: string[] = [];
+ let badLibs: string[] = [];
+
+ const result = await this.connection.sendQsh({
+ command: [
+ `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
+ ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
+ ].join(`; `)
+ });
+
+ if (result.stderr) {
+ const lines = result.stderr.split(`\n`);
+
+ lines.forEach(line => {
+ const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
+
+ // If there is an error about the library, store it
+ if (badLib) badLibs.push(badLib);
+ });
+ }
+
+ if (result && badLibs.length > 0) {
+ validLibs = libraryList.filter(lib => !badLibs.includes(lib));
+ }
+
+ return Promise.resolve({validLibs,badLibs});
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/typings.ts b/src/typings.ts
index 381b4e6cb..e902d7c73 100644
--- a/src/typings.ts
+++ b/src/typings.ts
@@ -240,4 +240,23 @@ export type SearchHit = {
export type SearchHitLine = {
number: number
content: string
-}
\ No newline at end of file
+}
+
+export interface MemberParts extends IBMiMember {
+ basename: string
+}
+
+
+export type RemoteApp = {
+ path: string
+ specific ?: string
+ names: string[]
+}
+
+export type RemoteApps = RemoteApp[];
+
+export type RemoteFeature = string | undefined;
+
+export type RemoteFeatures = { [name: string]: RemoteFeature };
+
+export type aspInfo = { [id: number]: string };
From 73ee37c3d6ec4d8ab96dbfe6ade2d64f504cb2f3 Mon Sep 17 00:00:00 2001
From: krethan
Date: Tue, 1 Oct 2024 23:38:26 -0600
Subject: [PATCH 26/28] Fixed few issues introduced with IBMiSettings class.
---
src/api/IBMi.ts | 30 +++++++++++++++-----------
src/api/IBMiSettings.ts | 48 ++++++++++++++++++++---------------------
2 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index a39860ef5..5290ba176 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -244,7 +244,7 @@ export default class IBMi {
let libraryListResult = await connSettings.getLibraryList();
if (libraryListResult.libStatus) {
-
+
this.defaultUserLibraries = libraryListResult.defaultUserLibraries;
//If this is the first time the config is made, then these arrays will be empty
@@ -410,12 +410,14 @@ export default class IBMi {
});
await remoteApps.checkRemoteFeatures(remoteApp, this);
- this.remoteFeatures = remoteApps.getRemoteFeatures();
} catch (e) {
console.log(e);
}
}
+
+ this.remoteFeatures = remoteApps.getRemoteFeatures();
+
}
if (this.sqlRunnerAvailable()) {
@@ -430,9 +432,11 @@ export default class IBMi {
//This is mostly a nice to have. We grab the ASP info so user's do
//not have to provide the ASP in the settings.
-
- this.aspInfo = await connSettings.getASPInfo();
- if (Object.keys(this.aspInfo).length === 0) {
+ try {
+ this.aspInfo = await connSettings.getASPInfo();
+ }
+ catch (e) {
+ //Oh well
progress.report({
message: `Failed to get ASP information.`
});
@@ -542,13 +546,13 @@ export default class IBMi {
});
if ((!quickConnect || !cachedServerSettings?.pathChecked)) {
-
+
const bashrcFile = `${homeResult.homeDir}/.bashrc`;
-
+
let bashrcExists = await connSettings.checkBashRCFile(bashrcFile);
-
+
let checkPathResult = await connSettings.checkPaths(["/QOpenSys/pkgs/bin", "/usr/bin", "/QOpenSys/usr/bin"]);
-
+
if (checkPathResult.reason && await vscode.window.showWarningMessage(`${checkPathResult.missingPath} not found in $PATH`, {
modal: true,
detail: `${checkPathResult.reason}, so Code for IBM i may not function correctly. Would you like to ${bashrcExists ? "update" : "create"} ${bashrcFile} to fix this now?`,
@@ -557,7 +561,7 @@ export default class IBMi {
this.appendOutput(`${bashrcExists ? "update" : "create"} ${bashrcFile}`);
if (!bashrcExists) {
//Create bashrc File
- let createBashResult = await connSettings.createBashrcFile(bashrcFile,connectionObject.username);
+ let createBashResult = await connSettings.createBashrcFile(bashrcFile, connectionObject.username);
//Error creating bashrc File
if (!createBashResult.createBash) {
vscode.window.showWarningMessage(`Error creating ${bashrcFile}):\n${createBashResult.createBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
@@ -566,7 +570,7 @@ export default class IBMi {
else {
//Update bashRC file
let updateBashResult = await connSettings.updateBashrcFile(bashrcFile);
- if(!updateBashResult.updateBash) {
+ if (!updateBashResult.updateBash) {
vscode.window.showWarningMessage(`Error modifying PATH in ${bashrcFile}):\n${updateBashResult.updateBashMsg}.\n\n Code for IBM i may not function correctly. Please contact your system administrator.`, { modal: true });
}
}
@@ -612,8 +616,8 @@ export default class IBMi {
message: `Validate configured library list`
});
- let libraryListResult = await connSettings.validateLibraryList(this.defaultUserLibraries,this.config.libraryList);
- if(libraryListResult.badLibs.length > 0) {
+ let libraryListResult = await connSettings.validateLibraryList(this.defaultUserLibraries, this.config.libraryList);
+ if (libraryListResult.badLibs.length > 0) {
const chosen = await vscode.window.showWarningMessage(`The following ${libraryListResult.badLibs.length > 1 ? `libraries` : `library`} does not exist: ${libraryListResult.badLibs.join(`,`)}. Remove ${libraryListResult.badLibs.length > 1 ? `them` : `it`} from the library list?`, `Yes`, `No`);
if (chosen === `Yes`) {
this.config!.libraryList = libraryListResult.validLibs;
diff --git a/src/api/IBMiSettings.ts b/src/api/IBMiSettings.ts
index ad5d07869..c3eb961a9 100644
--- a/src/api/IBMiSettings.ts
+++ b/src/api/IBMiSettings.ts
@@ -289,7 +289,7 @@ export default class IBMiSettings {
}
} catch (e) {
//Oh well
- aspInfo = {};
+ return Promise.reject(e);
}
return Promise.resolve(aspInfo);
@@ -399,14 +399,12 @@ export default class IBMiSettings {
}
// If reason is still undefined, then we know the user has all the required paths. Then we don't
// need to check for their existence before checking the order of the required paths.
- if (!reason) {
- for (let x = 1; x <= requiredPaths.length; x++) {
- if (currentPaths.indexOf(requiredPaths[0]) > currentPaths.indexOf(requiredPaths[x])) {
- reason = `${requiredPaths[0]} is not in the right position in your $PATH shell environment variable`;
- missingPath = requiredPaths[0];
- break;
- }
- }
+
+ if (!reason &&
+ (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
+ || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
+ reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
+ missingPath = "/QOpenSys/pkgs/bin"
}
return Promise.resolve({ reason: reason, missingPath: missingPath });
@@ -481,34 +479,34 @@ export default class IBMiSettings {
return Promise.resolve({ updateBash, updateBashMsg });
}
- async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{validLibs: string[], badLibs: string[]}> {
-
- let validLibs: string[] = [];
- let badLibs: string[] = [];
+ async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{ validLibs: string[], badLibs: string[] }> {
+
+ let validLibs: string[] = [];
+ let badLibs: string[] = [];
- const result = await this.connection.sendQsh({
+ const result = await this.connection.sendQsh({
command: [
- `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
- ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
+ `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
+ ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
].join(`; `)
- });
+ });
- if (result.stderr) {
+ if (result.stderr) {
const lines = result.stderr.split(`\n`);
lines.forEach(line => {
- const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
+ const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
- // If there is an error about the library, store it
- if (badLib) badLibs.push(badLib);
+ // If there is an error about the library, store it
+ if (badLib) badLibs.push(badLib);
});
- }
+ }
- if (result && badLibs.length > 0) {
+ if (result && badLibs.length > 0) {
validLibs = libraryList.filter(lib => !badLibs.includes(lib));
- }
+ }
- return Promise.resolve({validLibs,badLibs});
+ return Promise.resolve({ validLibs, badLibs });
}
From 6c5df527531b0f3352bb5241ce22d09817638a46 Mon Sep 17 00:00:00 2001
From: krethan
Date: Tue, 1 Oct 2024 23:41:30 -0600
Subject: [PATCH 27/28] Removed unneccessary comments
---
src/api/IBMi.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 5290ba176..8b5b8f4d0 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -310,12 +310,10 @@ export default class IBMi {
}
- //TO DO: why is this required????
const commandShellResult = await this.sendCommand({
command: `echo $SHELL`
});
- //TO DO: why is this required????
if (commandShellResult.code === 0) {
this.shell = commandShellResult.stdout.trim();
}
From f9d0e4c59c6be870bd94f6260581da208b07963a Mon Sep 17 00:00:00 2001
From: krethan
Date: Sat, 12 Oct 2024 21:14:24 -0600
Subject: [PATCH 28/28] Reuse checkObject from content and moved deleteObject
into content. Fixed indentation from 4 characters to 2.
---
src/api/IBMi.ts | 8 +-
src/api/IBMiApps.ts | 136 ++++----
src/api/IBMiContent.ts | 14 +-
src/api/IBMiSettings.ts | 720 +++++++++++++++++++---------------------
4 files changed, 433 insertions(+), 445 deletions(-)
diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts
index 8b5b8f4d0..28eb4c97c 100644
--- a/src/api/IBMi.ts
+++ b/src/api/IBMi.ts
@@ -326,7 +326,7 @@ export default class IBMi {
message: `Checking for bad data areas.`
});
- const QCPTOIMPF = await connSettings.checkObjectExists('QSYS', 'QCPTOIMPF', '*DTAARA');
+ const QCPTOIMPF = await this.content.checkObject({ library: 'QSYS', name: 'QCPTOIMPF', type: '*DTAARA' });
if (QCPTOIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPTOIMPF exists on this system and may impact Code for IBM i functionality.`, {
@@ -335,7 +335,7 @@ export default class IBMi {
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- connSettings.deleteObject('QSYS', 'QCPTOIMPF', '*DTAARA').then((result) => {
+ this.content.deleteObject({ library: 'QSYS', name: 'QCPTOIMPF', type: '*DTAARA' }).then((result) => {
if (result) {
vscode.window.showInformationMessage(`The data
area QSYS/QCPTOIMPF has been deleted.`);
@@ -354,7 +354,7 @@ export default class IBMi {
});
}
- const QCPFRMIMPF = await connSettings.checkObjectExists('QSYS', 'QCPFRMIMPF', '*DTAARA');
+ const QCPFRMIMPF = await this.content.checkObject({ library: 'QSYS', name: 'QCPFRMIMPF', type: '*DTAARA' });
if (QCPFRMIMPF) {
vscode.window.showWarningMessage(`The data area QSYS/QCPFRMIMPF exists on this system and may impact Code for IBM i functionality.`, {
@@ -362,7 +362,7 @@ export default class IBMi {
}, `Delete`, `Read more`).then(choice => {
switch (choice) {
case `Delete`:
- connSettings.deleteObject('QSYS', 'QCPFRMIMPF', '*DTAARA')
+ this.content.deleteObject({ library: 'QSYS', name: 'QCPFRMIMPF', type: '*DTAARA' })
.then((result) => {
if (result) {
vscode.window.showInformationMessage(`The data area QSYS/QCPFRMIMPF has been deleted.`);
diff --git a/src/api/IBMiApps.ts b/src/api/IBMiApps.ts
index abfc48974..b5527c122 100644
--- a/src/api/IBMiApps.ts
+++ b/src/api/IBMiApps.ts
@@ -3,85 +3,85 @@ import { RemoteApp, RemoteApps, RemoteFeatures } from "../typings";
export default class IBMiApps {
- private remoteApps: RemoteApps;
- private remoteFeatures: RemoteFeatures;
-
- constructor() {
- this.remoteApps = [
- {
- path: `/usr/bin/`,
- names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
- },
- {
- path: `/QOpenSys/pkgs/bin/`,
- names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
- },
- {
- path: `/QSYS.LIB/`,
- // In the future, we may use a generic specific.
- // Right now we only need one program
- // specific: `*.PGM`,
- specific: `QZDFMDB2.PGM`,
- names: [`QZDFMDB2.PGM`]
- },
- {
- path: `/QIBM/ProdData/IBMiDebugService/bin/`,
- specific: `startDebugService.sh`,
- names: [`startDebugService.sh`]
- }
- ];
-
- this.remoteFeatures = {};
- this.setRemoteFeatures();
-
+ private remoteApps: RemoteApps;
+ private remoteFeatures: RemoteFeatures;
+
+ constructor() {
+ this.remoteApps = [
+ {
+ path: `/usr/bin/`,
+ names: [`setccsid`, `iconv`, `attr`, `tar`, `ls`]
+ },
+ {
+ path: `/QOpenSys/pkgs/bin/`,
+ names: [`git`, `grep`, `tn5250`, `md5sum`, `bash`, `chsh`, `stat`, `sort`, `tar`, `ls`, `find`]
+ },
+ {
+ path: `/QSYS.LIB/`,
+ // In the future, we may use a generic specific.
+ // Right now we only need one program
+ // specific: `*.PGM`,
+ specific: `QZDFMDB2.PGM`,
+ names: [`QZDFMDB2.PGM`]
+ },
+ {
+ path: `/QIBM/ProdData/IBMiDebugService/bin/`,
+ specific: `startDebugService.sh`,
+ names: [`startDebugService.sh`]
+ }
+ ];
+
+ this.remoteFeatures = {};
+ this.initRemoteFeatures();
+
+ }
+
+ addRemoteApp(remoteApp: RemoteApp) {
+
+ //Add remote App
+ this.remoteApps.push(remoteApp);
+
+ //Add possible features to list
+ for (const name of remoteApp.names) {
+ this.remoteFeatures[name] = undefined;
}
- addRemoteApp(remoteApp: RemoteApp) {
-
- //Add remote App
- this.remoteApps.push(remoteApp);
+ }
- //Add possible features to list
- for(const name of remoteApp.names) {
- this.remoteFeatures[name] = undefined;
- }
+ getRemoteApps(): RemoteApps {
+ return this.remoteApps;
+ }
- }
+ initRemoteFeatures() {
- getRemoteApps(): RemoteApps {
- return this.remoteApps;
+ for (const feature of this.remoteApps) {
+ for (const name of feature.names) {
+ this.remoteFeatures[name] = undefined;
+ }
}
- setRemoteFeatures() {
+ }
- for (const feature of this.remoteApps) {
- for(const name of feature.names) {
- this.remoteFeatures[name] = undefined;
- }
- }
-
- }
+ getRemoteFeatures(): RemoteFeatures {
+ return this.remoteFeatures;
+ }
- getRemoteFeatures(): RemoteFeatures {
- return this.remoteFeatures;
- }
+ async checkRemoteFeatures(remoteApp: RemoteApp, connection: IBMi) {
- async checkRemoteFeatures(remoteApp: RemoteApp, connection: IBMi) {
-
- const call = await connection.sendCommand({ command: `ls -p ${remoteApp.path}${remoteApp.specific || ``}` });
- if (call.stdout) {
- const files = call.stdout.split(`\n`);
-
- if (remoteApp.specific) {
- for (const name of remoteApp.names)
- this.remoteFeatures[name] = files.find(file => file.includes(name));
- } else {
- for (const name of remoteApp.names)
- if (files.includes(name))
- this.remoteFeatures[name] = remoteApp.path + name;
- }
- }
+ const call = await connection.sendCommand({ command: `ls -p ${remoteApp.path}${remoteApp.specific || ``}` });
+ if (call.stdout) {
+ const files = call.stdout.split(`\n`);
+ if (remoteApp.specific) {
+ for (const name of remoteApp.names)
+ this.remoteFeatures[name] = files.find(file => file.includes(name));
+ } else {
+ for (const name of remoteApp.names)
+ if (files.includes(name))
+ this.remoteFeatures[name] = remoteApp.path + name;
+ }
}
+ }
+
}
\ No newline at end of file
diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts
index 0775b561a..1a5a1c096 100644
--- a/src/api/IBMiContent.ts
+++ b/src/api/IBMiContent.ts
@@ -906,6 +906,18 @@ export default class IBMiContent {
})).code === 0;
}
+ async deleteObject(object: { library: string, name: string, type: string }) {
+
+ return (await this.ibmi.runCommand({
+ command: this.toCl(`DLTOBJ`, {
+ obj: `${this.ibmi.upperCaseName(object.library)}/${this.ibmi.upperCaseName(object.name)}`,
+ objtype: object.type.toLocaleUpperCase()
+ }),
+ noLibList: true
+ })).code === 0;
+
+ }
+
async testStreamFile(path: string, right: "e" | "f" | "d" | "r" | "w" | "x") {
return (await this.ibmi.sendCommand({ command: `test -${right} ${Tools.escapePath(path)}` })).code === 0;
}
@@ -951,7 +963,7 @@ export default class IBMiContent {
return cl;
}
- async getAttributes(path: string | (QsysPath & { member?: string }), ...operands: AttrOperands[]) {
+ async getAttributes(path: string | (QsysPath & { member?: string }), ...operands: AttrOperands[]) {
const target = (path = typeof path === 'string' ? Tools.escapePath(path) : this.ibmi.sysNameInAmerican(Tools.qualifyPath(path.library, path.name, path.member, path.asp)));
const result = await this.ibmi.sendCommand({ command: `${this.ibmi.remoteFeatures.attr} -p ${target} ${operands.join(" ")}` });
diff --git a/src/api/IBMiSettings.ts b/src/api/IBMiSettings.ts
index c3eb961a9..e0b3755ab 100644
--- a/src/api/IBMiSettings.ts
+++ b/src/api/IBMiSettings.ts
@@ -7,507 +7,483 @@ const CCSID_SYSVAL = -2;
export default class IBMiSettings {
- constructor(private connection: IBMi) {
+ constructor(private connection: IBMi) {
- }
+ }
- async checkShellOutput(): Promise {
+ async checkShellOutput(): Promise {
- const checkShellText = `This should be the only text!`;
- const checkShellResult = await this.connection.sendCommand({
- command: `echo "${checkShellText}"`,
- directory: `.`
- });
+ const checkShellText = `This should be the only text!`;
+ const checkShellResult = await this.connection.sendCommand({
+ command: `echo "${checkShellText}"`,
+ directory: `.`
+ });
- return Promise.resolve(checkShellResult.stdout.split(`\n`)[0] == checkShellText);
+ return Promise.resolve(checkShellResult.stdout.split(`\n`)[0] == checkShellText);
- }
+ }
- async getHomeDirectory(): Promise<{ homeExists: boolean, homeDir: string, homeMsg: string }> {
+ async getHomeDirectory(): Promise<{ homeExists: boolean, homeDir: string, homeMsg: string }> {
- let homeDir;
- let homeMsg = '';
- let homeExists;
+ let homeDir;
+ let homeMsg = '';
+ let homeExists;
- const homeResult = await this.connection.sendCommand({
- command: `echo $HOME && cd && test -w $HOME`,
- directory: `.`
- });
+ const homeResult = await this.connection.sendCommand({
+ command: `echo $HOME && cd && test -w $HOME`,
+ directory: `.`
+ });
- // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
- // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
- // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
- // if we iterate on this code again in the future)
- // - stdout contains the name of the home directory (even if it does not exist)
- // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
- // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
-
- homeExists = homeResult.code == 0;
- homeDir = homeResult.stdout.trim();
-
- if (!homeExists) {
- // Let's try to provide more valuable information to the user about why their home directory
- // is bad and maybe even provide the opportunity to create the home directory
-
- // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
- homeExists = (0 === (await this.connection.sendCommand({ command: `test -e ${homeDir}` })).code);
- if (homeExists) {
- // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
- // But, remember, but we only got here if 'cd $HOME' failed.
- // Let's try to figure out why....
- if (0 !== (await this.connection.sendCommand({ command: `test -d ${homeDir}` })).code) {
- homeMsg = `Your home directory (${homeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`;
- }
- else if (0 !== (await this.connection.sendCommand({ command: `test -w ${homeDir}` })).code) {
- homeMsg = `Your home directory (${homeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`;
-
- }
- else if (0 !== (await this.connection.sendCommand({ command: `test -x ${homeDir}` })).code) {
- homeMsg = `Your home directory (${homeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`;
- }
- else {
- // not sure, but get your sys admin involved
- homeMsg = `Your home directory (${homeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`;
- }
- }
- else {
- homeMsg = `Your home directory (${homeDir}) does not exist, so Code for IBM i may not function correctly.`;
- }
- }
+ // Note: if the home directory does not exist, the behavior of the echo/cd/test command combo is as follows:
+ // - stderr contains 'Could not chdir to home directory /home/________: No such file or directory'
+ // (The output contains 'chdir' regardless of locale and shell, so maybe we could use that
+ // if we iterate on this code again in the future)
+ // - stdout contains the name of the home directory (even if it does not exist)
+ // - The 'cd' command causes an error if the home directory does not exist or otherwise can't be cd'ed into
+ // - The 'test' command causes an error if the home directory is not writable (one can cd into a non-writable directory)
+
+ homeExists = homeResult.code == 0;
+ homeDir = homeResult.stdout.trim();
+
+ if (!homeExists) {
+ // Let's try to provide more valuable information to the user about why their home directory
+ // is bad and maybe even provide the opportunity to create the home directory
- return Promise.resolve({ homeExists, homeDir, homeMsg });
+ // we _could_ just assume the home directory doesn't exist but maybe there's something more going on, namely mucked-up permissions
+ homeExists = (0 === (await this.connection.sendCommand({ command: `test -e ${homeDir}` })).code);
+ if (homeExists) {
+ // Note: this logic might look backward because we fall into this (failure) leg on what looks like success (home dir exists).
+ // But, remember, but we only got here if 'cd $HOME' failed.
+ // Let's try to figure out why....
+ if (0 !== (await this.connection.sendCommand({ command: `test -d ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not a directory! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -w ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not writable! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else if (0 !== (await this.connection.sendCommand({ command: `test -x ${homeDir}` })).code) {
+ homeMsg = `Your home directory (${homeDir}) is not usable due to permissions! Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ else {
+ // not sure, but get your sys admin involved
+ homeMsg = `Your home directory (${homeDir}) exists but is unusable. Code for IBM i may not function correctly. Please contact your system administrator.`;
+ }
+ }
+ else {
+ homeMsg = `Your home directory (${homeDir}) does not exist, so Code for IBM i may not function correctly.`;
+ }
}
- async createHomeDirectory(homeDir: string, username: string): Promise<{ homeCreated: boolean, homeMsg: string }> {
+ return Promise.resolve({ homeExists, homeDir, homeMsg });
- let homeCreated = false;
- let homeMsg = '';
+ }
- const homeCmd = `mkdir -p ${homeDir} && chown ${username.toLowerCase()} ${homeDir} && chmod 0755 ${homeDir}`;
+ async createHomeDirectory(homeDir: string, username: string): Promise<{ homeCreated: boolean, homeMsg: string }> {
- let mkHomeResult = await this.connection.sendCommand({ command: homeCmd, directory: `.` });
+ let homeCreated = false;
+ let homeMsg = '';
- if (0 === mkHomeResult.code) {
- homeCreated = true;
- } else {
- let mkHomeErrs = mkHomeResult.stderr;
- // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
- homeMsg = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
- }
+ const homeCmd = `mkdir -p ${homeDir} && chown ${username.toLowerCase()} ${homeDir} && chmod 0755 ${homeDir}`;
- return Promise.resolve({ homeCreated, homeMsg });
+ let mkHomeResult = await this.connection.sendCommand({ command: homeCmd, directory: `.` });
+ if (0 === mkHomeResult.code) {
+ homeCreated = true;
+ } else {
+ let mkHomeErrs = mkHomeResult.stderr;
+ // We still get 'Could not chdir to home directory' in stderr so we need to hackily gut that out, as well as the bashisms that are a side effect of our API
+ homeMsg = mkHomeErrs.substring(1 + mkHomeErrs.indexOf(`\n`)).replace(`bash: line 1: `, ``);
}
- async getLibraryList(): Promise<{ libStatus: boolean, currentLibrary: string, defaultUserLibraries: string[] }> {
-
+ return Promise.resolve({ homeCreated, homeMsg });
+ }
- //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
- //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
+ async getLibraryList(): Promise<{ libStatus: boolean, currentLibrary: string, defaultUserLibraries: string[] }> {
- let currentLibrary = `QGPL`;
- let defaultUserLibraries = [];
- let libStatus = false;
- const liblResult = await this.connection.sendQsh({
- command: `liblist`
- });
- if (liblResult.code === 0) {
- libStatus = true;
- const libraryListString = liblResult.stdout;
- if (libraryListString !== ``) {
- const libraryList = libraryListString.split(`\n`);
-
- let lib, type;
- for (const line of libraryList) {
- lib = line.substring(0, 10).trim();
- type = line.substring(12);
-
- switch (type) {
- case `USR`:
- defaultUserLibraries.push(lib);
- break;
-
- case `CUR`:
- currentLibrary = lib;
- break;
- }
- }
- }
- }
+ //Since the compiles are stateless, then we have to set the library list each time we use the `SYSTEM` command
+ //We setup the defaultUserLibraries here so we can remove them later on so the user can setup their own library list
- return Promise.resolve({ libStatus, currentLibrary, defaultUserLibraries });
+ let currentLibrary = `QGPL`;
+ let defaultUserLibraries = [];
+ let libStatus = false;
- }
+ const liblResult = await this.connection.sendQsh({
+ command: `liblist`
+ });
- async setTempLibrary(tempLibrary: string): Promise {
+ if (liblResult.code === 0) {
+ libStatus = true;
+ const libraryListString = liblResult.stdout;
+ if (libraryListString !== ``) {
+ const libraryList = libraryListString.split(`\n`);
- let tempLibrarySet = false;
+ let lib, type;
+ for (const line of libraryList) {
+ lib = line.substring(0, 10).trim();
+ type = line.substring(12);
- //Check the temp lib (where temp outfile data lives) exists
- const createdTempLib = await this.connection.runCommand({
- command: `CRTLIB LIB(${tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
- noLibList: true
- });
+ switch (type) {
+ case `USR`:
+ defaultUserLibraries.push(lib);
+ break;
- if (createdTempLib.code === 0) {
- tempLibrarySet = true;
+ case `CUR`:
+ currentLibrary = lib;
+ break;
+ }
}
- else {
- const messages = Tools.parseMessages(createdTempLib.stderr);
- if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
- tempLibrarySet = true;
- }
- else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
- const tempLibExists = await this.connection.runCommand({
- command: `CHKOBJ OBJ(QSYS/${tempLibrary}) OBJTYPE(*LIB)`,
- noLibList: true
- });
-
- if (tempLibExists.code === 0) {
- //We're all good if no errors
- tempLibrarySet = true;
- }
- else {
- tempLibrarySet = false;
- }
+ }
+ }
- }
- }
+ return Promise.resolve({ libStatus, currentLibrary, defaultUserLibraries });
- return Promise.resolve(tempLibrarySet);
+ }
- }
+ async setTempLibrary(tempLibrary: string): Promise {
- async setTempDirectory(tempDir: string): Promise {
+ let tempLibrarySet = false;
- let tempDirSet = false;
+ //Check the temp lib (where temp outfile data lives) exists
+ const createdTempLib = await this.connection.runCommand({
+ command: `CRTLIB LIB(${tempLibrary}) TEXT('Code for i temporary objects. May be cleared.')`,
+ noLibList: true
+ });
- // Check if the temp directory exists
- let result = await this.connection.sendCommand({
- command: `[ -d "${tempDir}" ]`
+ if (createdTempLib.code === 0) {
+ tempLibrarySet = true;
+ }
+ else {
+ const messages = Tools.parseMessages(createdTempLib.stderr);
+ if (messages.findId(`CPF2158`) || messages.findId(`CPF2111`)) { //Already exists, hopefully ok :)
+ tempLibrarySet = true;
+ }
+ else if (messages.findId(`CPD0032`)) { //Can't use CRTLIB
+ const tempLibExists = await this.connection.runCommand({
+ command: `CHKOBJ OBJ(QSYS/${tempLibrary}) OBJTYPE(*LIB)`,
+ noLibList: true
});
- if (result.code === 0) {
- // Directory exists
- tempDirSet = true;
- } else {
- // Directory does not exist, try to create it
- let result = await this.connection.sendCommand({
- command: `mkdir -p ${tempDir}`
- });
- if (result.code === 0) {
- // Directory created
- tempDirSet = true;
- } else {
- // Directory not created
- }
+ if (tempLibExists.code === 0) {
+ //We're all good if no errors
+ tempLibrarySet = true;
+ }
+ else {
+ tempLibrarySet = false;
}
- return Promise.resolve(tempDirSet);
+ }
+ }
+ return Promise.resolve(tempLibrarySet);
+
+ }
+
+ async setTempDirectory(tempDir: string): Promise {
+
+ let tempDirSet = false;
+
+ // Check if the temp directory exists
+ let result = await this.connection.sendCommand({
+ command: `[ -d "${tempDir}" ]`
+ });
+
+ if (result.code === 0) {
+ // Directory exists
+ tempDirSet = true;
+ } else {
+ // Directory does not exist, try to create it
+ let result = await this.connection.sendCommand({
+ command: `mkdir -p ${tempDir}`
+ });
+ if (result.code === 0) {
+ // Directory created
+ tempDirSet = true;
+ } else {
+ // Directory not created
+ }
}
- async clearTempLibrary(tempLibrary: string): Promise {
+ return Promise.resolve(tempDirSet);
- let clearMsg = '';
+ }
- this.connection.runCommand({
- command: `DLTOBJ OBJ(${tempLibrary}/O_*) OBJTYPE(*FILE)`,
- noLibList: true,
- })
- .then(result => {
- // All good!
- if (result && result.stderr) {
- const messages = Tools.parseMessages(result.stderr);
- if (!messages.findId(`CPF2125`)) {
- clearMsg = `Temporary data not cleared from ${tempLibrary}.`;
- }
- }
- });
+ async clearTempLibrary(tempLibrary: string): Promise {
- return Promise.resolve(clearMsg);
+ let clearMsg = '';
- }
+ this.connection.runCommand({
+ command: `DLTOBJ OBJ(${tempLibrary}/O_*) OBJTYPE(*FILE)`,
+ noLibList: true,
+ })
+ .then(result => {
+ // All good!
+ if (result && result.stderr) {
+ const messages = Tools.parseMessages(result.stderr);
+ if (!messages.findId(`CPF2125`)) {
+ clearMsg = `Temporary data not cleared from ${tempLibrary}.`;
+ }
+ }
+ });
- async clearTempDirectory(tempDir: string): Promise {
+ return Promise.resolve(clearMsg);
- let clearMsg = '';
+ }
- this.connection.sendCommand({
- command: `rm -rf ${path.posix.join(tempDir, `vscodetemp*`)}`
- })
- .then(result => {
- // All good!
- })
- .catch(e => {
- // CPF2125: No objects deleted.
- clearMsg = `Temporary data not cleared from ${tempDir}.`;
- });
+ async clearTempDirectory(tempDir: string): Promise {
- return Promise.resolve(clearMsg);
+ let clearMsg = '';
+ try {
+ this.connection.sendCommand({
+ command: `rm -rf ${path.posix.join(tempDir, `vscodetemp*`)}`
+ });
+ }
+ catch (e) {
+ // CPF2125: No objects deleted.
+ clearMsg = `Temporary data not cleared from ${tempDir}.`;
}
- async checkObjectExists(library: string, object: string, type: string): Promise {
-
- const objResult = await this.connection.runCommand({
- command: `CHKOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
- noLibList: true
- });
+ return Promise.resolve(clearMsg);
- return Promise.resolve(objResult.code === 0);
+ }
- }
+ async getASPInfo(): Promise {
- async deleteObject(library: string, object: string, type: string): Promise {
+ let aspInfo: aspInfo = {};
- const deleteResult = await this.connection.runCommand({
- command: `DLTOBJ OBJ(${library}/${object}) OBJTYPE(${type})`,
- noLibList: true
+ try {
+ const resultSet = await this.connection.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
+ if (resultSet.length) {
+ resultSet.forEach(row => {
+ if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
+ aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
+ }
});
-
- return Promise.resolve(deleteResult.code === 0);
-
+ }
+ } catch (e) {
+ //Oh well
+ return Promise.reject(e);
}
- async getASPInfo(): Promise {
+ return Promise.resolve(aspInfo);
- let aspInfo: aspInfo = {};
+ }
- try {
- const resultSet = await this.connection.runSQL(`SELECT * FROM QSYS2.ASP_INFO`);
- if (resultSet.length) {
- resultSet.forEach(row => {
- if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) {
- aspInfo[Number(row.ASP_NUMBER)] = String(row.DEVICE_DESCRIPTION_NAME);
- }
- });
- }
- } catch (e) {
- //Oh well
- return Promise.reject(e);
- }
+ async getQCCSID(): Promise {
- return Promise.resolve(aspInfo);
+ let qccsid = 0;
+ const [systemCCSID] = await this.connection.runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
+ if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
+ qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
}
- async getQCCSID(): Promise {
+ return Promise.resolve(qccsid);
- let qccsid = 0;
+ }
- const [systemCCSID] = await this.connection.runSQL(`select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`);
- if (typeof systemCCSID.CURRENT_NUMERIC_VALUE === 'number') {
- qccsid = systemCCSID.CURRENT_NUMERIC_VALUE;
- }
+ async getjobCCSID(userName: string): Promise {
- return Promise.resolve(qccsid);
+ let jobCCSID = CCSID_SYSVAL;
+ const [userInfo] = await this.connection.runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${userName}') ) )`);
+ if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
+ jobCCSID = userInfo.CHARACTER_CODE_SET_ID;
}
- async getjobCCSID(userName: string): Promise {
+ return Promise.resolve(jobCCSID);
- let jobCCSID = CCSID_SYSVAL;
+ }
- const [userInfo] = await this.connection.runSQL(`select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${userName}') ) )`);
- if (userInfo.CHARACTER_CODE_SET_ID !== `null` && typeof userInfo.CHARACTER_CODE_SET_ID === 'number') {
- jobCCSID = userInfo.CHARACTER_CODE_SET_ID;
- }
+ async getDefaultCCSID(): Promise {
- return Promise.resolve(jobCCSID);
+ let userDefaultCCSID = 0;
+ try {
+ const [activeJob] = await this.connection.runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
+ userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
+ }
+ catch (error) {
+ const [defaultCCSID] = (await this.connection.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
+ .stdout
+ .split("\n")
+ .filter(line => line.includes("DFTCCSID"));
+
+ const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
+ if (defaultCCSCID && !isNaN(defaultCCSCID)) {
+ userDefaultCCSID = defaultCCSCID;
+ }
}
- async getDefaultCCSID(): Promise {
+ return Promise.resolve(userDefaultCCSID);
- let userDefaultCCSID = 0;
+ }
- try {
- const [activeJob] = await this.connection.runSQL(`Select DEFAULT_CCSID From Table(QSYS2.ACTIVE_JOB_INFO( JOB_NAME_FILTER => '*', DETAILED_INFO => 'ALL' ))`);
- userDefaultCCSID = Number(activeJob.DEFAULT_CCSID);
- }
- catch (error) {
- const [defaultCCSID] = (await this.connection.runCommand({ command: "DSPJOB OPTION(*DFNA)" }))
- .stdout
- .split("\n")
- .filter(line => line.includes("DFTCCSID"));
-
- const defaultCCSCID = Number(defaultCCSID.split("DFTCCSID").at(1)?.trim());
- if (defaultCCSCID && !isNaN(defaultCCSCID)) {
- userDefaultCCSID = defaultCCSCID;
- }
- }
+ async getLocalEncodingValues(): Promise {
- return Promise.resolve(userDefaultCCSID);
+ let localEncoding = '';
+ const [variants] = await this.connection.runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
+ + ` values ( cast( x'7B' as varchar(1) )`
+ + ` , cast( x'7C' as varchar(1) )`
+ + ` , cast( x'5B' as varchar(1) ) )`
+ + `)`
+ + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+
+ if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
+ localEncoding = variants.LOCAL;
}
- async getLocalEncodingValues(): Promise {
+ return Promise.resolve(localEncoding);
- let localEncoding = '';
+ }
- const [variants] = await this.connection.runSQL(`With VARIANTS ( HASH, AT, DOLLARSIGN ) as (`
- + ` values ( cast( x'7B' as varchar(1) )`
- + ` , cast( x'7C' as varchar(1) )`
- + ` , cast( x'5B' as varchar(1) ) )`
- + `)`
- + `Select HASH concat AT concat DOLLARSIGN as LOCAL from VARIANTS`);
+ async setBash(): Promise {
- if (typeof variants.LOCAL === 'string' && variants.LOCAL !== `null`) {
- localEncoding = variants.LOCAL;
- }
+ let bashset = false;
- return Promise.resolve(localEncoding);
+ const commandSetBashResult = await this.connection.sendCommand({
+ command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
+ });
- }
+ if (!commandSetBashResult.stderr) bashset = true;
- async setBash(): Promise {
+ return Promise.resolve(bashset);
- let bashset = false;
+ }
- const commandSetBashResult = await this.connection.sendCommand({
- command: `/QOpenSys/pkgs/bin/chsh -s /QOpenSys/pkgs/bin/bash`
- });
+ async getEnvironmentVariable(envVar: string): Promise {
+ return (await this.connection.sendCommand({ command: `echo ${envVar}` })).stdout.split(":");
+ }
- if (!commandSetBashResult.stderr) bashset = true;
+ async checkPaths(requiredPaths: string[]): Promise<{ reason: string, missingPath: string }> {
- return Promise.resolve(bashset);
+ const currentPaths = await this.getEnvironmentVariable('$PATH');
- }
+ let reason = '';
+ let missingPath = '';
- async getEnvironmentVariable(envVar: string): Promise {
- return (await this.connection.sendCommand({ command: `echo ${envVar}` })).stdout.split(":");
+ for (const requiredPath of requiredPaths) {
+ if (!currentPaths.includes(requiredPath)) {
+ reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
+ missingPath = requiredPath
+ break;
+ }
+ }
+ // If reason is still undefined, then we know the user has all the required paths. Then we don't
+ // need to check for their existence before checking the order of the required paths.
+
+ if (!reason &&
+ (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
+ || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
+ reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
+ missingPath = "/QOpenSys/pkgs/bin"
}
- async checkPaths(requiredPaths: string[]): Promise<{ reason: string, missingPath: string }> {
+ return Promise.resolve({ reason: reason, missingPath: missingPath });
- const currentPaths = await this.getEnvironmentVariable('$PATH');
+ }
- let reason = '';
- let missingPath = '';
+ async checkBashRCFile(bashrcFile: string): Promise {
- for (const requiredPath of requiredPaths) {
- if (!currentPaths.includes(requiredPath)) {
- reason = `Your $PATH shell environment variable does not include ${requiredPath}`;
- missingPath = requiredPath
- break;
- }
- }
- // If reason is still undefined, then we know the user has all the required paths. Then we don't
- // need to check for their existence before checking the order of the required paths.
-
- if (!reason &&
- (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/usr/bin")
- || (currentPaths.indexOf("/QOpenSys/pkgs/bin") > currentPaths.indexOf("/QOpenSys/usr/bin")))) {
- reason = "/QOpenSys/pkgs/bin is not in the right position in your $PATH shell environment variable";
- missingPath = "/QOpenSys/pkgs/bin"
- }
+ let bashrcExists = false;
- return Promise.resolve({ reason: reason, missingPath: missingPath });
+ bashrcExists = (await this.connection.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
- }
+ return Promise.resolve(bashrcExists);
+ }
- async checkBashRCFile(bashrcFile: string): Promise {
+ async createBashrcFile(bashrcFile: string, username: string): Promise<{ createBash: boolean, createBashMsg: string }> {
- let bashrcExists = false;
+ let createBash = true;
+ let createBashMsg = '';
- bashrcExists = (await this.connection.sendCommand({ command: `test -e ${bashrcFile}` })).code === 0;
+ // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
+ // all the required paths, but we don't overwrite the priority of other items on their path.
+ const createBashrc = await this.connection.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
- return Promise.resolve(bashrcExists);
+ if (createBashrc.code !== 0) {
+ createBash = false;
+ createBashMsg = createBashrc.stderr;
}
- async createBashrcFile(bashrcFile: string, username: string): Promise<{ createBash: boolean, createBashMsg: string }> {
+ return Promise.resolve({ createBash, createBashMsg });
- let createBash = true;
- let createBashMsg = '';
+ }
- // Add "/usr/bin" and "/QOpenSys/usr/bin" to the end of the path. This way we know that the user has
- // all the required paths, but we don't overwrite the priority of other items on their path.
- const createBashrc = await this.connection.sendCommand({ command: `echo "# Generated by Code for IBM i\nexport PATH=/QOpenSys/pkgs/bin:\\$PATH:/QOpenSys/usr/bin:/usr/bin" >> ${bashrcFile} && chown ${username.toLowerCase()} ${bashrcFile} && chmod 755 ${bashrcFile}` });
+ async updateBashrcFile(bashrcFile: string): Promise<{ updateBash: boolean, updateBashMsg: string }> {
- if (createBashrc.code !== 0) {
- createBash = false;
- createBashMsg = createBashrc.stderr;
- }
+ let updateBash = true;
+ let updateBashMsg = '';
- return Promise.resolve({ createBash, createBashMsg });
-
- }
-
- async updateBashrcFile(bashrcFile: string): Promise<{ updateBash: boolean, updateBashMsg: string }> {
-
- let updateBash = true;
- let updateBashMsg = '';
-
- try {
- const content = this.connection.content;
- if (content) {
- const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
- let replaced = false;
- bashrcContent.forEach((line, index) => {
- if (!replaced) {
- const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
- if (pathRegex) {
- bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
- .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
- .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
- replaced = true;
- }
- }
- });
-
- if (!replaced) {
- bashrcContent.push(
- "",
- "# Generated by Code for IBM i",
- "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
- );
- }
-
- await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
+ try {
+ const content = this.connection.content;
+ if (content) {
+ const bashrcContent = (await content.downloadStreamfile(bashrcFile)).split("\n");
+ let replaced = false;
+ bashrcContent.forEach((line, index) => {
+ if (!replaced) {
+ const pathRegex = /^((?:export )?PATH=)(.*)(?:)$/.exec(line);
+ if (pathRegex) {
+ bashrcContent[index] = `${pathRegex[1]}/QOpenSys/pkgs/bin:${pathRegex[2]
+ .replace("/QOpenSys/pkgs/bin", "") //Removes /QOpenSys/pkgs/bin wherever it is
+ .replace("::", ":")}:/QOpenSys/usr/bin:/usr/bin`; //Removes double : in case /QOpenSys/pkgs/bin wasn't at the end
+ replaced = true;
}
- }
- catch (error) {
- updateBash = false;
- updateBashMsg = error;
+ }
+ });
+
+ if (!replaced) {
+ bashrcContent.push(
+ "",
+ "# Generated by Code for IBM i",
+ "export PATH=/QOpenSys/pkgs/bin:$PATH:/QOpenSys/usr/bin:/usr/bin"
+ );
}
- return Promise.resolve({ updateBash, updateBashMsg });
+ await content.writeStreamfile(bashrcFile, bashrcContent.join("\n"));
+ }
+ }
+ catch (error) {
+ updateBash = false;
+ updateBashMsg = error;
}
- async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{ validLibs: string[], badLibs: string[] }> {
+ return Promise.resolve({ updateBash, updateBashMsg });
+ }
- let validLibs: string[] = [];
- let badLibs: string[] = [];
+ async validateLibraryList(defaultUserLibraries: string[], libraryList: string[]): Promise<{ validLibs: string[], badLibs: string[] }> {
- const result = await this.connection.sendQsh({
- command: [
- `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
- ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
- ].join(`; `)
- });
+ let validLibs: string[] = [];
+ let badLibs: string[] = [];
- if (result.stderr) {
- const lines = result.stderr.split(`\n`);
+ const result = await this.connection.sendQsh({
+ command: [
+ `liblist -d ` + defaultUserLibraries.join(` `).replace(/\$/g, `\\$`),
+ ...libraryList.map(lib => `liblist -a ` + lib.replace(/\$/g, `\\$`))
+ ].join(`; `)
+ });
- lines.forEach(line => {
- const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
+ if (result.stderr) {
+ const lines = result.stderr.split(`\n`);
- // If there is an error about the library, store it
- if (badLib) badLibs.push(badLib);
- });
- }
+ lines.forEach(line => {
+ const badLib = libraryList.find(lib => line.includes(`ibrary ${lib} `));
- if (result && badLibs.length > 0) {
- validLibs = libraryList.filter(lib => !badLibs.includes(lib));
- }
-
- return Promise.resolve({ validLibs, badLibs });
+ // If there is an error about the library, store it
+ if (badLib) badLibs.push(badLib);
+ });
+ }
+ if (result && badLibs.length > 0) {
+ validLibs = libraryList.filter(lib => !badLibs.includes(lib));
}
+ return Promise.resolve({ validLibs, badLibs });
+
+ }
}
\ No newline at end of file