From aac87eb5dafa04e8e66ec1da5dd5adc4c713a8d3 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 3 Feb 2025 13:25:39 -0500 Subject: [PATCH 1/6] Refactor temporary library and directory checks into separate methods for improved readability and maintainability Signed-off-by: worksofliam --- src/api/IBMi.ts | 164 +++++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 70 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 4644f3bc4..b8852b648 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -247,8 +247,6 @@ export default class IBMi { this.appendOutput(`Code for IBM i, version ${currentExtensionVersion}\n\n`); - let tempLibrarySet = false; - callbacks.progress({ message: `Loading configuration.` }); @@ -428,10 +426,17 @@ export default class IBMi { 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`); + [ + this.remoteFeatures.jdk80, + this.remoteFeatures.jdk11, + this.remoteFeatures.openjdk11, + this.remoteFeatures.jdk17 + ] = await Promise.all([ + javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk80/64bit`), + javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk11/64bit`), + javaCheck(`/QOpensys/pkgs/lib/jvm/openjdk-11`), + javaCheck(`/QOpenSys/QIBM/ProdData/JavaVM/jdk17/64bit`) + ]); } if (this.remoteFeatures.uname) { @@ -506,64 +511,14 @@ export default class IBMi { } callbacks.progress({ - message: `Checking temporary library configuration.` - }); - - //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 + message: `Checking temporary directory and temporary library configuration.` }); - 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.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; - } - } - } - callbacks.progress({ - 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 - } - } + const [tempLibrarySet, tempDirSet] = await Promise.all([ + this.ensureTempLibraryExists(currentLibrary), + this.ensureTempDirectory() + ]); if (!tempDirSet) { this.config.tempDir = `/tmp`; @@ -618,20 +573,21 @@ 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, QCPFRMIMPF] = await Promise.all([ + this.runCommand({ + command: `CHKOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`, + noLibList: true + }), + this.runCommand({ + command: `CHKOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`, + noLibList: true + }) + ]); if (QCPTOIMPF?.code === 0) { callbacks.uiErrorHandler(this, `QCPTOIMPF_exists`); } - const QCPFRMIMPF = await this.runCommand({ - command: `CHKOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`, - noLibList: true - }); - if (QCPFRMIMPF?.code === 0) { callbacks.uiErrorHandler(this, `QCPFRMIMPF_exists`); } @@ -975,6 +931,74 @@ export default class IBMi { } } + private async ensureTempLibraryExists(fallbackTempLib: string) { + let tempLibrarySet: boolean = false; + + if (!this.config) { + return false; + } + + 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 = 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 (fallbackTempLib && !fallbackTempLib.startsWith(`Q`)) { + //Using ${currentLibrary} as the temporary library for temporary data. + this.config.tempLibrary = fallbackTempLib; + tempLibrarySet = true; + } + } + } + + return tempLibrarySet; + } + + private async ensureTempDirectory() { + let tempDirSet: boolean = false; + + if (!this.config) { + return false; + } + + 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 + } + } + return tempDirSet; + } + /** * Can return 0 if the OS version was not detected. */ From 8de22bacabc85284b888e011ea9845e48e1db6e6 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 3 Feb 2025 14:29:04 -0500 Subject: [PATCH 2/6] Ability to cache component states Signed-off-by: worksofliam --- src/api/IBMi.ts | 8 ++-- src/api/components/component.ts | 7 +++ src/api/components/cqsh/index.ts | 4 ++ src/api/components/getMemberInfo.ts | 3 +- src/api/components/getNewLibl.ts | 3 +- src/api/components/manager.ts | 45 ++++++++++++++++--- .../configuration/storage/CodeForIStorage.ts | 2 + 7 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index b8852b648..128e7e02e 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -458,9 +458,10 @@ export default class IBMi { } callbacks.progress({ message: `Checking Code for IBM i components.` }); - await this.componentManager.startup(); - const componentStates = await this.componentManager.getState(); + this.componentManager.startup(cachedServerSettings?.installedComponents); + + const componentStates = await this.componentManager.getInstallState(); this.appendOutput(`\nCode for IBM i components:\n`); for (const state of componentStates) { this.appendOutput(`\t${state.id.name} (${state.id.version}): ${state.state}\n`); @@ -895,6 +896,7 @@ export default class IBMi { qccsid: this.qccsid, jobCcsid: this.userJobCcsid, remoteFeatures: this.remoteFeatures, + installedComponents: this.componentManager.getInstallState(), remoteFeaturesKeys: Object.keys(this.remoteFeatures).sort().toString(), badDataAreasChecked: true, libraryListValidated: true, @@ -1284,7 +1286,7 @@ export default class IBMi { } getComponentStates() { - return this.componentManager.getState(); + return this.componentManager.getInstallState(); } /** diff --git a/src/api/components/component.ts b/src/api/components/component.ts index 4b67952b8..24729b740 100644 --- a/src/api/components/component.ts +++ b/src/api/components/component.ts @@ -7,6 +7,11 @@ export type ComponentIdentification = { version: number } +export type ComponentInstallState = { + id: ComponentIdentification + state: ComponentState +} + /** * Defines a component that is managed per IBM i. * @@ -38,6 +43,8 @@ export type IBMiComponent = { */ getIdentification(): ComponentIdentification; + setInstallDirectory?(installDirectory: string): Promise; + /** * @returns the component's {@link ComponentState state} on the IBM i */ diff --git a/src/api/components/cqsh/index.ts b/src/api/components/cqsh/index.ts index 5c70143ca..d88b478c5 100644 --- a/src/api/components/cqsh/index.ts +++ b/src/api/components/cqsh/index.ts @@ -23,6 +23,10 @@ export class CustomQSh implements IBMiComponent { return `${id.name}_${id.version}`; } + async setInstallDirectory(installDirectory: string): Promise { + this.installPath = path.posix.join(installDirectory, this.getFileName()); + } + async getRemoteState(connection: IBMi, installDirectory: string): Promise { this.installPath = path.posix.join(installDirectory, this.getFileName()); const result = await connection.content.testStreamFile(this.installPath, "x"); diff --git a/src/api/components/getMemberInfo.ts b/src/api/components/getMemberInfo.ts index e6d005a60..69f9a7cb7 100644 --- a/src/api/components/getMemberInfo.ts +++ b/src/api/components/getMemberInfo.ts @@ -15,7 +15,7 @@ export class GetMemberInfo implements IBMiComponent { } getIdentification() { - return { name: GetMemberInfo.ID, version: this.installedVersion }; + return { name: GetMemberInfo.ID, version: this.currentVersion }; } async getRemoteState(connection: IBMi): Promise { @@ -47,6 +47,7 @@ export class GetMemberInfo implements IBMiComponent { if (result.code) { return `Error`; } else { + this.installedVersion = this.currentVersion; return `Installed`; } }); diff --git a/src/api/components/getNewLibl.ts b/src/api/components/getNewLibl.ts index e6f292407..20ff4a034 100644 --- a/src/api/components/getNewLibl.ts +++ b/src/api/components/getNewLibl.ts @@ -13,7 +13,7 @@ export class GetNewLibl implements IBMiComponent { } getIdentification() { - return { name: GetNewLibl.ID, version: this.installedVersion }; + return { name: GetNewLibl.ID, version: this.currentVersion }; } async getRemoteState(connection: IBMi): Promise { @@ -45,6 +45,7 @@ export class GetNewLibl implements IBMiComponent { }); if (!result.code) { + this.installedVersion = this.currentVersion; return `Installed`; } else { return `Error`; diff --git a/src/api/components/manager.ts b/src/api/components/manager.ts index 3ce8b5f20..0416641c0 100644 --- a/src/api/components/manager.ts +++ b/src/api/components/manager.ts @@ -1,6 +1,6 @@ import IBMi from "../IBMi"; -import { ComponentState, IBMiComponent } from "./component"; +import { ComponentIdentification, ComponentInstallState, ComponentState, IBMiComponent } from "./component"; interface ExtensionContextI { extension: { @@ -37,11 +37,13 @@ export const extensionComponentRegistry = new ComponentRegistry(); export class ComponentManager { private readonly registered: Map = new Map; - constructor(private readonly connection: IBMi) { + constructor(private readonly connection: IBMi) {} + public getComponentIds(): ComponentIdentification[] { + return Array.from(extensionComponentRegistry.getComponents().values()).flatMap(a => a.flat()).map(c => c.getIdentification()); } - public getState() { + public getInstallState(): ComponentInstallState[] { return Array.from(this.registered.keys()).map(k => { const comp = this.registered.get(k)!; return { @@ -51,11 +53,38 @@ export class ComponentManager { }); } - public async startup() { + public shouldUpdate(lastInstalled: ComponentInstallState[] = []) { + return this.getComponentIds().some(c => { + const installed = lastInstalled.find(i => i.id.name === c.name); + if (!installed) { + return true; + }; + + const sameVersion = (installed.id.version === c.version); + if (!sameVersion) { + return true; + } + + return false; + }); + } + + public async startup(lastInstalled: ComponentInstallState[] = []) { const components = Array.from(extensionComponentRegistry.getComponents().values()).flatMap(a => a.flat()); for (const component of components) { await component.reset?.(); - this.registered.set(component.getIdentification().name, await new IBMiComponentRuntime(this.connection, component).check()); + const newComponent = new IBMiComponentRuntime(this.connection, component); + + const installed = lastInstalled.find(i => i.id.name === component.getIdentification().name); + const sameVersion = installed && (installed.id.version === component.getIdentification().version); + + if (!installed || !sameVersion) { + await newComponent.check(); + } else { + newComponent.overrideState(installed.state); + } + + this.registered.set(component.getIdentification().name, newComponent); } } @@ -92,6 +121,12 @@ class IBMiComponentRuntime { return this.state; } + async overrideState(newState: ComponentState) { + const installDir = await this.getInstallDirectory(); + await this.component.setInstallDirectory?.(installDir); + this.state = newState; + } + async check() { try { const installDirectory = await this.getInstallDirectory(); diff --git a/src/api/configuration/storage/CodeForIStorage.ts b/src/api/configuration/storage/CodeForIStorage.ts index 30e95c5bd..6a581d75e 100644 --- a/src/api/configuration/storage/CodeForIStorage.ts +++ b/src/api/configuration/storage/CodeForIStorage.ts @@ -1,3 +1,4 @@ +import { ComponentInstallState } from "../../components/component"; import { ConnectionData } from "../../types"; import { BaseStorage } from "./BaseStorage"; const SERVER_SETTINGS_CACHE_PREFIX = `serverSettingsCache_`; @@ -20,6 +21,7 @@ export type CachedServerSettings = { qccsid: number | null; jobCcsid: number | null remoteFeatures: { [name: string]: string | undefined } + installedComponents: ComponentInstallState[], remoteFeaturesKeys: string | null badDataAreasChecked: boolean | null libraryListValidated: boolean | null From 576698795c6b0ff90ddd5bd3d2325d395afe4982 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 3 Feb 2025 14:44:31 -0500 Subject: [PATCH 3/6] Update component startup logic to handle quick connect Signed-off-by: worksofliam --- 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 128e7e02e..461709aa4 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -459,7 +459,7 @@ export default class IBMi { callbacks.progress({ message: `Checking Code for IBM i components.` }); - this.componentManager.startup(cachedServerSettings?.installedComponents); + this.componentManager.startup(quickConnect() ? cachedServerSettings?.installedComponents : []); const componentStates = await this.componentManager.getInstallState(); this.appendOutput(`\nCode for IBM i components:\n`); From aaa93237722e09568faa50e89bb0f443399d1e89 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 4 Feb 2025 09:21:55 -0500 Subject: [PATCH 4/6] Remove unused manager method Signed-off-by: worksofliam --- src/api/components/manager.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/api/components/manager.ts b/src/api/components/manager.ts index 0416641c0..0d67c78a9 100644 --- a/src/api/components/manager.ts +++ b/src/api/components/manager.ts @@ -53,22 +53,6 @@ export class ComponentManager { }); } - public shouldUpdate(lastInstalled: ComponentInstallState[] = []) { - return this.getComponentIds().some(c => { - const installed = lastInstalled.find(i => i.id.name === c.name); - if (!installed) { - return true; - }; - - const sameVersion = (installed.id.version === c.version); - if (!sameVersion) { - return true; - } - - return false; - }); - } - public async startup(lastInstalled: ComponentInstallState[] = []) { const components = Array.from(extensionComponentRegistry.getComponents().values()).flatMap(a => a.flat()); for (const component of components) { From e33cbac03fc8fd67f784a56563d34a02017608c6 Mon Sep 17 00:00:00 2001 From: LJ <3708366+worksofliam@users.noreply.github.com> Date: Tue, 4 Feb 2025 09:22:30 -0500 Subject: [PATCH 5/6] Update src/api/IBMi.ts Co-authored-by: Sanjula Ganepola <32170854+SanjulaGanepola@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 461709aa4..f708e57dc 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -896,7 +896,7 @@ export default class IBMi { qccsid: this.qccsid, jobCcsid: this.userJobCcsid, remoteFeatures: this.remoteFeatures, - installedComponents: this.componentManager.getInstallState(), + installedComponents: componentStates, remoteFeaturesKeys: Object.keys(this.remoteFeatures).sort().toString(), badDataAreasChecked: true, libraryListValidated: true, From 0c033884ea97cae48dd7dbcd9671f8f5280bad6e Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 4 Feb 2025 22:43:05 -0500 Subject: [PATCH 6/6] Add all-important await Signed-off-by: worksofliam --- 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 f708e57dc..1f490d259 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -459,7 +459,7 @@ export default class IBMi { callbacks.progress({ message: `Checking Code for IBM i components.` }); - this.componentManager.startup(quickConnect() ? cachedServerSettings?.installedComponents : []); + await this.componentManager.startup(quickConnect() ? cachedServerSettings?.installedComponents : []); const componentStates = await this.componentManager.getInstallState(); this.appendOutput(`\nCode for IBM i components:\n`);