From af03505edada1deb84a27627b213eacb79355346 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 21 Mar 2023 19:42:20 +0100 Subject: [PATCH 01/12] Add Quick Connect --- src/api/Configuration.ts | 4 +- src/api/IBMi.ts | 366 ++++++++++++++++++--------------- src/api/Storage.ts | 19 +- src/webviews/settings/index.ts | 1 + 4 files changed, 223 insertions(+), 167 deletions(-) diff --git a/src/api/Configuration.ts b/src/api/Configuration.ts index 2b19f4875..4fd36d76e 100644 --- a/src/api/Configuration.ts +++ b/src/api/Configuration.ts @@ -45,6 +45,7 @@ export namespace ConnectionConfiguration { debugUpdateProductionFiles: boolean; debugEnableDebugTracing: boolean; readOnlyMode: boolean; + quickConnect: boolean; [name: string]: any; } @@ -119,7 +120,8 @@ export namespace ConnectionConfiguration { debugIsSecure: (parameters.debugIsSecure === true), debugUpdateProductionFiles: (parameters.debugUpdateProductionFiles === true), debugEnableDebugTracing: (parameters.debugEnableDebugTracing === true), - readOnlyMode: (parameters.readOnlyMode === true) + readOnlyMode: (parameters.readOnlyMode === true), + quickConnect: (parameters.quickConnect === true) } } diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index de52d984b..ca3e3b5a1 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -9,6 +9,8 @@ import { CompileTools } from "./CompileTools"; import { ConnectionData, CommandData, StandardIO, CommandResult, IBMiMember, RemoteCommand } from "../typings"; import * as configVars from './configVars'; import { instance } from "../instantiate"; +import IBMiContent from "./IBMiContent"; +import { GlobalStorage, CachedServerSettings } from './Storage'; export interface MemberParts extends IBMiMember { basename: string @@ -161,6 +163,9 @@ export default class IBMi { //Load existing config this.config = await ConnectionConfiguration.load(this.currentConnectionName); + // Load cached server settings. + const cachedServerSettings: CachedServerSettings = GlobalStorage.get().getServerSettingsCache(this.currentConnectionName); + progress.report({ message: `Checking home directory.` }); @@ -371,107 +376,116 @@ export default class IBMi { }); } - progress.report({ - message: `Checking for bad data areas.` - }); - - try { - await this.remoteCommand( - `CHKOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`, - undefined - ); - - 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.remoteCommand( - `DLTOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)` - ) - .then(() => { - vscode.window.showInformationMessage(`The data area QSYS/QCPTOIMPF has been deleted.`); - }) - .catch(e => { - 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/halcyon-tech/vscode-ibmi/issues/476#issuecomment-1018908018`)); - break; - } - }); - } catch (e) { - // It doesn't exist, we're all good. - } - - try { - await this.remoteCommand( - `CHKOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`, - undefined - ); - - 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.remoteCommand( - `DLTOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)` - ) - .then(() => { - vscode.window.showInformationMessage(`The data area QSYS/QCPFRMIMPF has been deleted.`); - }) - .catch(e => { - vscode.window.showInformationMessage(`Failed to delete the data area QSYS/QCPFRMIMPF. Code for IBM i may not work as intended.`); - }); - break; - case `Read more`: - vscode.env.openExternal(vscode.Uri.parse(`https://github.com/halcyon-tech/vscode-ibmi/issues/476#issuecomment-1018908018`)); - break; - } + // Check for bad data areas? + if (this.config.quickConnect === true && cachedServerSettings?.badDataAreasChecked === true) { + } else { + progress.report({ + message: `Checking for bad data areas.` }); - } catch (e) { - // It doesn't exist, we're all good. - } - - progress.report({ - message: `Checking installed components on host IBM i.` - }); - // We need to check if our remote programs are installed. - remoteApps.push( - { - path: `/QSYS.lib/${this.config.tempLibrary.toUpperCase()}.lib/`, - names: [`GENCMDXML.PGM`, `GETNEWLIBL.PGM`], - specific: `GE*.PGM` + try { + await this.remoteCommand( + `CHKOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)`, + undefined + ); + + 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.remoteCommand( + `DLTOBJ OBJ(QSYS/QCPTOIMPF) OBJTYPE(*DTAARA)` + ) + .then(() => { + vscode.window.showInformationMessage(`The data area QSYS/QCPTOIMPF has been deleted.`); + }) + .catch(e => { + 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/halcyon-tech/vscode-ibmi/issues/476#issuecomment-1018908018`)); + break; + } + }); + } catch (e) { + // It doesn't exist, we're all good. } - ); - //Next, we see what pase features are available (installed via yum) - //This may enable certain features in the future. - for (const feature of remoteApps) { try { - progress.report({ - message: `Checking installed components on host IBM i: ${feature.path}` + await this.remoteCommand( + `CHKOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)`, + undefined + ); + + 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.remoteCommand( + `DLTOBJ OBJ(QSYS/QCPFRMIMPF) OBJTYPE(*DTAARA)` + ) + .then(() => { + vscode.window.showInformationMessage(`The data area QSYS/QCPFRMIMPF has been deleted.`); + }) + .catch(e => { + vscode.window.showInformationMessage(`Failed to delete the data area QSYS/QCPFRMIMPF. Code for IBM i may not work as intended.`); + }); + break; + case `Read more`: + vscode.env.openExternal(vscode.Uri.parse(`https://github.com/halcyon-tech/vscode-ibmi/issues/476#issuecomment-1018908018`)); + break; + } }); + } catch (e) { + // It doesn't exist, we're all good. + } + } - const call = await this.paseCommand(`ls -p ${feature.path}${feature.specific || ``}`); - if (typeof call === `string`) { - const files = call.split(`\n`); + // Check for installed components? + if (this.config.quickConnect === true && cachedServerSettings?.remoteFeatures) { + this.remoteFeatures = cachedServerSettings.remoteFeatures; + } else { + progress.report({ + message: `Checking installed components on host IBM i.` + }); - 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; + // We need to check if our remote programs are installed. + remoteApps.push( + { + path: `/QSYS.lib/${this.config.tempLibrary.toUpperCase()}.lib/`, + names: [`GENCMDXML.PGM`, `GETNEWLIBL.PGM`], + specific: `GE*.PGM` + } + ); + + //Next, we see what pase features are available (installed via yum) + //This may enable certain features in the future. + for (const feature of remoteApps) { + try { + progress.report({ + message: `Checking installed components on host IBM i: ${feature.path}` + }); + + const call = await this.paseCommand(`ls -p ${feature.path}${feature.specific || ``}`); + if (typeof call === `string`) { + const files = call.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; + } } + } catch (e) { + console.log(e); } - } catch (e) { - console.log(e); } } @@ -479,101 +493,112 @@ export default class IBMi { let statement; let output; - progress.report({ - message: `Checking for ASP information.` - }); - - //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 { - statement = `SELECT * FROM QSYS2.ASP_INFO`; - output = await this.paseCommand(`LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, undefined, 0, { - stdin: statement + // Check for ASP information? + if (this.config.quickConnect === true && cachedServerSettings?.aspInfo) { + this.aspInfo = cachedServerSettings.aspInfo; + } else { + progress.report({ + message: `Checking for ASP information.` }); - if (typeof output === `string`) { - const rows = Tools.db2Parse(output); - rows.forEach((row: any) => { - if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) { - this.aspInfo[row.ASP_NUMBER] = row.DEVICE_DESCRIPTION_NAME; - } + //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 { + statement = `SELECT * FROM QSYS2.ASP_INFO`; + output = await this.paseCommand(`LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, undefined, 0, { + stdin: statement + }); + + if (typeof output === `string`) { + const rows = Tools.db2Parse(output); + rows.forEach((row: any) => { + if (row.DEVICE_DESCRIPTION_NAME && row.DEVICE_DESCRIPTION_NAME !== `null`) { + this.aspInfo[row.ASP_NUMBER] = row.DEVICE_DESCRIPTION_NAME; + } + }); + } + } catch (e) { + //Oh well + progress.report({ + message: `Failed to get ASP information.` }); } - } catch (e) { - //Oh well - progress.report({ - message: `Failed to get ASP information.` - }); } - progress.report({ - message: `Fetching conversion values.` - }); - - // 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 { - const CCSID_SYSVAL = -2; - statement = `select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`; - output = await this.sendCommand({ - command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, - stdin: statement + // Fetch conversion values? + if (this.config.quickConnect === true && cachedServerSettings?.qccsid !== null && cachedServerSettings?.variantChars) { + this.qccsid = cachedServerSettings.qccsid; + this.variantChars = cachedServerSettings.variantChars; + } else { + progress.report({ + message: `Fetching conversion values.` }); - if (output.stdout) { - const [row] = Tools.db2Parse(output.stdout); - if (row && row.CHARACTER_CODE_SET_ID !== `null` && typeof row.CHARACTER_CODE_SET_ID === 'number') { - this.qccsid = row.CHARACTER_CODE_SET_ID; - } - } - - if (this.qccsid === undefined || this.qccsid === CCSID_SYSVAL) { - statement = `select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`; + // 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 { + const CCSID_SYSVAL = -2; + statement = `select CHARACTER_CODE_SET_ID from table( QSYS2.QSYUSRINFO( USERNAME => upper('${this.currentUser}') ) )`; output = await this.sendCommand({ command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, stdin: statement }); if (output.stdout) { - const rows = Tools.db2Parse(output.stdout); - const ccsid = rows.find(row => row.SYSTEM_VALUE_NAME === `QCCSID`); - if (ccsid && typeof ccsid.CURRENT_NUMERIC_VALUE === 'number') { - this.qccsid = ccsid.CURRENT_NUMERIC_VALUE; + const [row] = Tools.db2Parse(output.stdout); + if (row && row.CHARACTER_CODE_SET_ID !== `null` && typeof row.CHARACTER_CODE_SET_ID === 'number') { + this.qccsid = row.CHARACTER_CODE_SET_ID; } } - } - if (this.config.enableSQL && this.qccsid === 65535) { - this.config.enableSQL = false; - vscode.window.showErrorMessage(`QCCSID is set to 65535. Disabling SQL support.`); - } + if (this.qccsid === undefined || this.qccsid === CCSID_SYSVAL) { + statement = `select SYSTEM_VALUE_NAME, CURRENT_NUMERIC_VALUE from QSYS2.SYSTEM_VALUE_INFO where SYSTEM_VALUE_NAME = 'QCCSID'`; + output = await this.sendCommand({ + command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, + stdin: statement + }); - progress.report({ - message: `Fetching local encoding values.` - }); + if (output.stdout) { + const rows = Tools.db2Parse(output.stdout); + const ccsid = rows.find(row => row.SYSTEM_VALUE_NAME === `QCCSID`); + if (ccsid && typeof ccsid.CURRENT_NUMERIC_VALUE === 'number') { + this.qccsid = ccsid.CURRENT_NUMERIC_VALUE; + } + } + } - statement = `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; `; - output = await this.sendCommand({ - command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, - stdin: statement - }); - if (output.stdout) { - const [row] = Tools.db2Parse(output.stdout); - if (row && row.LOCAL !== `null` && typeof row.LOCAL === 'string') { - this.variantChars.local = row.LOCAL; + if (this.config.enableSQL && this.qccsid === 65535) { + this.config.enableSQL = false; + vscode.window.showErrorMessage(`QCCSID is set to 65535. Disabling SQL support.`); } - } else { - throw new Error(`There was an error running the SQL statement.`); + + progress.report({ + message: `Fetching local encoding values.` + }); + + statement = `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; `; + output = await this.sendCommand({ + command: `LC_ALL=EN_US.UTF-8 system "call QSYS/QZDFMDB2 PARM('-d' '-i')"`, + stdin: statement + }); + if (output.stdout) { + const [row] = Tools.db2Parse(output.stdout); + if (row && row.LOCAL !== `null` && typeof row.LOCAL === 'string') { + this.variantChars.local = row.LOCAL; + } + } else { + throw new Error(`There was an error running the SQL statement.`); + } + } catch (e) { + // Oh well! + console.log(e); } - } catch (e) { - // Oh well! - console.log(e); } } else { // Disable it if it's not found @@ -658,6 +683,17 @@ export default class IBMi { instance.fire("connected"); } + GlobalStorage.get().setServerSettingsCache(this.currentConnectionName, { + aspInfo: this.aspInfo, + qccsid: this.qccsid, + remoteFeatures: this.remoteFeatures, + variantChars: { + american: this.variantChars.american, + local: this.variantChars.local, + }, + badDataAreasChecked: true + }); + return { success: true }; diff --git a/src/api/Storage.ts b/src/api/Storage.ts index 01ee79c19..2097fca2c 100644 --- a/src/api/Storage.ts +++ b/src/api/Storage.ts @@ -4,7 +4,8 @@ const PREVIOUS_CUR_LIBS_KEY = `prevCurLibs`; const LAST_PROFILE_KEY = `currentProfile`; const SOURCE_LIST_KEY = `sourceList`; const DEPLOYMENT_KEY = `deployment`; -const DEBUG_KEY = `debug` +const DEBUG_KEY = `debug`; +const SERVER_SETTINGS_CACHE_KEY = `serverSettingsCache`; export type PathContent = Record; export type DeploymentPath = Record; @@ -33,6 +34,14 @@ export type LastConnection = { timestamp: number }; +export type CachedServerSettings = { + aspInfo: { [id: number]: string }; + qccsid: number | null; + remoteFeatures: { [name: string]: string | undefined }; + variantChars: { american: string, local: string }; + badDataAreasChecked: boolean | null +} | undefined; + export class GlobalStorage extends Storage { private static instance: GlobalStorage; @@ -73,6 +82,14 @@ export class GlobalStorage extends Storage { async setLastConnections(lastConnections: LastConnection[]) { await this.set("lastConnections", lastConnections.sort((c1, c2) => c2.timestamp - c1.timestamp)); } + + getServerSettingsCache(name: string) { + return this.get(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`)); + } + + async setServerSettingsCache(name: string, serverSettings: CachedServerSettings) { + await this.set(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`), serverSettings); + } } export class ConnectionStorage extends Storage { diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index 1030af482..6c9c1abe5 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -55,6 +55,7 @@ export class SettingsUI { const featuresTab = new Section(); featuresTab + .addCheckbox(`quickConnect`, `Quick Connect`, `When enabled, server settings from previous connection will be used, resulting in much quicker connection. If server settings are changed, disable this setting at least once to pick up the new settings from the server.`, config.quickConnect) .addCheckbox(`enableSQL`, `Enable SQL`, `Must be enabled to make the use of SQL and is enabled by default. If you find SQL isn't working for some reason, disable this. If your QCCSID is 65535, it is recommend SQL is disabled. When disabled, will use import files where possible.`, config.enableSQL) .addCheckbox(`showDescInLibList`, `Show description of libraries in User Library List view`, `When enabled, library text and attribute will be shown in User Library List. It is recommended to also enable SQL for this.`, config.showDescInLibList) .addCheckbox(`autoConvertIFSccsid`, `Support EBCDIC streamfiles`, `Enable converting EBCDIC to UTF-8 when opening streamfiles. When disabled, assumes all streamfiles are in UTF8. When enabled, will open streamfiles regardless of encoding. May slow down open and save operations.

You can find supported CCSIDs with /usr/bin/iconv -l`, config.autoConvertIFSccsid) From 2d1dc79e8f2c54618e4948054eecec6bb85253ae Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 21 Mar 2023 20:14:48 +0100 Subject: [PATCH 02/12] Delete cached server settings when deleting connection --- src/api/Storage.ts | 5 ++++- src/views/ConnectionBrowser.ts | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api/Storage.ts b/src/api/Storage.ts index 2097fca2c..10df27242 100644 --- a/src/api/Storage.ts +++ b/src/api/Storage.ts @@ -90,7 +90,10 @@ export class GlobalStorage extends Storage { async setServerSettingsCache(name: string, serverSettings: CachedServerSettings) { await this.set(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`), serverSettings); } -} + + async deleteServerSettingsCache(name: string) { + await this.set(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`), undefined); + }} export class ConnectionStorage extends Storage { private connectionName: string = ""; diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index bb46e7f52..278ca6886 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -84,6 +84,9 @@ export class ObjectBrowserProvider { const newConnectionSettings = connectionSettings.filter(connection => connection.name !== server.name); await GlobalConfiguration.set(`connectionSettings`, newConnectionSettings); + // Also remove the cached connection settings + GlobalStorage.get().deleteServerSettingsCache(server.name); + // Then remove the password context.secrets.delete(`${server.name}_password`); From 0c4637ee7c0bc732a5870218d59526b77ae7a62d Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Sat, 25 Mar 2023 15:27:18 +0100 Subject: [PATCH 03/12] Add connect and reload option in connection right-click --- package.json | 14 ++++++++++++++ src/api/IBMi.ts | 12 +++++++----- src/views/ConnectionBrowser.ts | 13 ++++++++++--- src/webviews/login/index.ts | 4 ++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 14aaf8484..2f8269059 100644 --- a/package.json +++ b/package.json @@ -877,6 +877,11 @@ "category": "IBM i", "icon": "$(remote)" }, + { + "command": "code-for-ibmi.connectToAndReload", + "title": "Connect and Reload Server Settings", + "category": "IBM i" + }, { "command": "code-for-ibmi.refreshConnections", "title": "Refresh Connections", @@ -1520,6 +1525,10 @@ "command": "code-for-ibmi.connectToPrevious", "when": "never" }, + { + "command": "code-for-ibmi.connectToAndReload", + "when": "never" + }, { "command": "code-for-ibmi.disconnect", "when": "code-for-ibmi:connected" @@ -1727,6 +1736,11 @@ "when": "view == connectionBrowser && viewItem == server", "group": "inline" }, + { + "command": "code-for-ibmi.connectToAndReload", + "when": "view == connectionBrowser && viewItem == server", + "group": "3_connect@1" + }, { "command": "code-for-ibmi.moveLibraryUp", "when": "view == libraryListView && viewItem == library", diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index ca3e3b5a1..7df409c25 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -107,7 +107,7 @@ export default class IBMi { /** * @returns {Promise<{success: boolean, error?: any}>} Was succesful at connecting or not. */ - async connect(connectionObject: ConnectionData, reconnecting?: boolean): Promise<{ success: boolean, error?: any }> { + async connect(connectionObject: ConnectionData, reconnecting?: boolean, reloadServerSettings: boolean = false): Promise<{ success: boolean, error?: any }> { try { connectionObject.keepaliveInterval = 35000; // Make sure we're not passing any blank strings, as node_ssh will try to validate it @@ -165,6 +165,8 @@ export default class IBMi { // Load cached server settings. const cachedServerSettings: CachedServerSettings = GlobalStorage.get().getServerSettingsCache(this.currentConnectionName); + // Reload server settings? + const quickConnect = (this.config.quickConnect === true && reloadServerSettings === false); progress.report({ message: `Checking home directory.` @@ -377,7 +379,7 @@ export default class IBMi { } // Check for bad data areas? - if (this.config.quickConnect === true && cachedServerSettings?.badDataAreasChecked === true) { + if (quickConnect === true && cachedServerSettings?.badDataAreasChecked === true) { } else { progress.report({ message: `Checking for bad data areas.` @@ -446,7 +448,7 @@ export default class IBMi { } // Check for installed components? - if (this.config.quickConnect === true && cachedServerSettings?.remoteFeatures) { + if (quickConnect === true && cachedServerSettings?.remoteFeatures) { this.remoteFeatures = cachedServerSettings.remoteFeatures; } else { progress.report({ @@ -494,7 +496,7 @@ export default class IBMi { let output; // Check for ASP information? - if (this.config.quickConnect === true && cachedServerSettings?.aspInfo) { + if (quickConnect === true && cachedServerSettings?.aspInfo) { this.aspInfo = cachedServerSettings.aspInfo; } else { progress.report({ @@ -526,7 +528,7 @@ export default class IBMi { } // Fetch conversion values? - if (this.config.quickConnect === true && cachedServerSettings?.qccsid !== null && cachedServerSettings?.variantChars) { + if (quickConnect === true && cachedServerSettings?.qccsid !== null && cachedServerSettings?.variantChars) { this.qccsid = cachedServerSettings.qccsid; this.variantChars = cachedServerSettings.variantChars; } else { diff --git a/src/views/ConnectionBrowser.ts b/src/views/ConnectionBrowser.ts index 278ca6886..e9f95bfd1 100644 --- a/src/views/ConnectionBrowser.ts +++ b/src/views/ConnectionBrowser.ts @@ -33,7 +33,7 @@ export class ObjectBrowserProvider { } }), - vscode.commands.registerCommand(`code-for-ibmi.connectTo`, async (name?: string | Server) => { + vscode.commands.registerCommand(`code-for-ibmi.connectTo`, async (name?: string | Server, reloadServerSettings?: boolean) => { if (!this._attemptingConnection) { this._attemptingConnection = true; @@ -49,10 +49,10 @@ export class ObjectBrowserProvider { switch (typeof name) { case `string`: // Name of connection object - await Login.LoginToPrevious(name, context); + await Login.LoginToPrevious(name, context, reloadServerSettings); break; case `object`: // A Server object - await Login.LoginToPrevious(name.name, context); + await Login.LoginToPrevious(name.name, context, reloadServerSettings); break; default: vscode.window.showErrorMessage(`Use the Server Browser to select which system to connect to.`); @@ -63,6 +63,13 @@ export class ObjectBrowserProvider { } }), + vscode.commands.registerCommand(`code-for-ibmi.connectToAndReload`, async (server: Server) => { + if (!this._attemptingConnection && server) { + const reloadServerSettings = true; + vscode.commands.executeCommand(`code-for-ibmi.connectTo`, server.name, reloadServerSettings); + } + }), + vscode.commands.registerCommand(`code-for-ibmi.refreshConnections`, () => { this.refresh(); }), diff --git a/src/webviews/login/index.ts b/src/webviews/login/index.ts index 56a075150..6c5572870 100644 --- a/src/webviews/login/index.ts +++ b/src/webviews/login/index.ts @@ -115,7 +115,7 @@ export class Login { * @param name Connection name * @param context */ - static async LoginToPrevious(name: string, context: vscode.ExtensionContext) { + static async LoginToPrevious(name: string, context: vscode.ExtensionContext, reloadServerSettings?: boolean) { const connection = instance.getConnection(); if (connection) { // If the user is already connected and trying to connect to a different system, disconnect them first @@ -143,7 +143,7 @@ export class Login { } try { - const connected = await new IBMi().connect(connectionConfig); + const connected = await new IBMi().connect(connectionConfig, reloadServerSettings); if (connected.success) { vscode.window.showInformationMessage(`Connected to ${connectionConfig.host}!`); } else { From 500c1cd05b30f651058b40e640b6f47c8aca8664 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Sat, 25 Mar 2023 17:33:31 +0100 Subject: [PATCH 04/12] Make server settings cache key a function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Huge thanks to @sebjulliand for suggesting this! Co-authored-by: Sébastien Julliand --- src/api/Storage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/Storage.ts b/src/api/Storage.ts index 10df27242..400436bfc 100644 --- a/src/api/Storage.ts +++ b/src/api/Storage.ts @@ -5,7 +5,7 @@ const LAST_PROFILE_KEY = `currentProfile`; const SOURCE_LIST_KEY = `sourceList`; const DEPLOYMENT_KEY = `deployment`; const DEBUG_KEY = `debug`; -const SERVER_SETTINGS_CACHE_KEY = `serverSettingsCache`; +const SERVER_SETTINGS_CACHE_KEY = (name : string) => `serverSettingsCache_${name}`; export type PathContent = Record; export type DeploymentPath = Record; @@ -84,15 +84,15 @@ export class GlobalStorage extends Storage { } getServerSettingsCache(name: string) { - return this.get(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`)); + return this.get(SERVER_SETTINGS_CACHE_KEY(name)); } async setServerSettingsCache(name: string, serverSettings: CachedServerSettings) { - await this.set(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`), serverSettings); + await this.set(SERVER_SETTINGS_CACHE_KEY(name), serverSettings); } async deleteServerSettingsCache(name: string) { - await this.set(SERVER_SETTINGS_CACHE_KEY.concat(`_${name}`), undefined); + await this.set(SERVER_SETTINGS_CACHE_KEY(name)), undefined); }} export class ConnectionStorage extends Storage { From b748a50d137f260725ce3b0ad5fe59db529408f6 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 4 Apr 2023 00:50:06 +0200 Subject: [PATCH 05/12] Fix parenthesis error in deleteServerSettingsCache --- src/api/Storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Storage.ts b/src/api/Storage.ts index 400436bfc..4320f581c 100644 --- a/src/api/Storage.ts +++ b/src/api/Storage.ts @@ -92,7 +92,7 @@ export class GlobalStorage extends Storage { } async deleteServerSettingsCache(name: string) { - await this.set(SERVER_SETTINGS_CACHE_KEY(name)), undefined); + await this.set(SERVER_SETTINGS_CACHE_KEY(name), undefined); }} export class ConnectionStorage extends Storage { From 068155661ca048e23f6c5ea67310ce5be1317e43 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 4 Apr 2023 00:56:53 +0200 Subject: [PATCH 06/12] Check for match of remote features keys and cache --- src/api/IBMi.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 7df409c25..94eafac37 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -88,7 +88,9 @@ export default class IBMi { 'GENCMDXML.PGM': undefined, 'GETNEWLIBL.PGM': undefined, 'QZDFMDB2.PGM': undefined, - 'startDebugService.sh': undefined + 'startDebugService.sh': undefined, + attr: undefined, + iconv: undefined }; this.variantChars = { @@ -448,7 +450,8 @@ export default class IBMi { } // Check for installed components? - if (quickConnect === true && cachedServerSettings?.remoteFeatures) { + // For Quick Connect to work here, 'remoteFeatures' MUST have all features defined and no new properties may be added! + if (quickConnect === true && cachedServerSettings?.remoteFeatures && Object.keys(cachedServerSettings.remoteFeatures).sort().toString() === Object.keys(this.remoteFeatures).sort().toString()) { this.remoteFeatures = cachedServerSettings.remoteFeatures; } else { progress.report({ From 8df637f89e05f0895dc3fd60ab1245d2c4673933 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Sat, 8 Apr 2023 00:26:50 +0200 Subject: [PATCH 07/12] Clarify empty code block --- src/api/IBMi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 94eafac37..1c81fbaae 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -382,6 +382,7 @@ export default class IBMi { // Check for bad data areas? if (quickConnect === true && cachedServerSettings?.badDataAreasChecked === true) { + // Do nothing, bad data areas are already checked. } else { progress.report({ message: `Checking for bad data areas.` From 4c82cfb73ecce858f5191442557fc54c90053997 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Sat, 8 Apr 2023 00:58:07 +0200 Subject: [PATCH 08/12] Make Quick Connect active by default --- src/api/Configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Configuration.ts b/src/api/Configuration.ts index 4fd36d76e..685cdb62a 100644 --- a/src/api/Configuration.ts +++ b/src/api/Configuration.ts @@ -121,7 +121,7 @@ export namespace ConnectionConfiguration { debugUpdateProductionFiles: (parameters.debugUpdateProductionFiles === true), debugEnableDebugTracing: (parameters.debugEnableDebugTracing === true), readOnlyMode: (parameters.readOnlyMode === true), - quickConnect: (parameters.quickConnect === true) + quickConnect: (parameters.quickConnect === true || parameters.quickConnect === undefined) } } From 0b9b42b2fe813769430d59824ba75e6af6664979 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Sat, 8 Apr 2023 01:02:35 +0200 Subject: [PATCH 09/12] Clarify how to connect and reload server settings --- src/webviews/settings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index 6c9c1abe5..a9a9cd3f3 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -55,7 +55,7 @@ export class SettingsUI { const featuresTab = new Section(); featuresTab - .addCheckbox(`quickConnect`, `Quick Connect`, `When enabled, server settings from previous connection will be used, resulting in much quicker connection. If server settings are changed, disable this setting at least once to pick up the new settings from the server.`, config.quickConnect) + .addCheckbox(`quickConnect`, `Quick Connect`, `When enabled, server settings from previous connection will be used, resulting in much quicker connection. If server settings are changed, right-click the connection in Connection Browser and select Connect and Reload Server Settings to refresh the cache.`, config.quickConnect) .addCheckbox(`enableSQL`, `Enable SQL`, `Must be enabled to make the use of SQL and is enabled by default. If you find SQL isn't working for some reason, disable this. If your QCCSID is 65535, it is recommend SQL is disabled. When disabled, will use import files where possible.`, config.enableSQL) .addCheckbox(`showDescInLibList`, `Show description of libraries in User Library List view`, `When enabled, library text and attribute will be shown in User Library List. It is recommended to also enable SQL for this.`, config.showDescInLibList) .addCheckbox(`autoConvertIFSccsid`, `Support EBCDIC streamfiles`, `Enable converting EBCDIC to UTF-8 when opening streamfiles. When disabled, assumes all streamfiles are in UTF8. When enabled, will open streamfiles regardless of encoding. May slow down open and save operations.

You can find supported CCSIDs with /usr/bin/iconv -l`, config.autoConvertIFSccsid) From f8469c77bb6c15008d03e734bebc3ea7ef40dcb1 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 11 Apr 2023 16:22:50 +0200 Subject: [PATCH 10/12] Initialize new config parameters in settings view --- src/webviews/settings/index.ts | 55 ++++++++++------------------------ 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index a9a9cd3f3..2fda5d5c0 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -25,25 +25,16 @@ export class SettingsUI { const connectionSettings = GlobalConfiguration.get(`connectionSettings`); const connection = instance.getConnection(); - let name: string; - let existingConfigIndex: number = -1; let config: ConnectionConfiguration.Parameters; if (connectionSettings && server) { - name = server.name; - existingConfigIndex = connectionSettings.findIndex(connection => connection.name === name); - - if (existingConfigIndex >= 0) { - config = connectionSettings[existingConfigIndex]; - } else { - vscode.window.showErrorMessage(`Connection ${name} not found`); - return; - } + config = await ConnectionConfiguration.load(server.name); } else { config = instance.getConfig()!; if (connection && config) { - name = config.name; + // Reload config to initialize any new config parameters. + config = await ConnectionConfiguration.load(config.name); } else { vscode.window.showErrorMessage(`No connection is active.`); return; @@ -149,7 +140,7 @@ export class SettingsUI { .addHorizontalRule() .addButtons({ id: `save`, label: `Save settings` }); - const page = await ui.loadPage(`Settings: ${name}`); + const page = await ui.loadPage(`Settings: ${config.name}`); if (page && page.data) { page.panel.dispose(); @@ -172,34 +163,20 @@ export class SettingsUI { } } - if (server) { - if (connectionSettings && existingConfigIndex >= 0) { - config = { - ...config, - ...data, - }; - - connectionSettings[existingConfigIndex] = config; - await GlobalConfiguration.set(`connectionSettings`, connectionSettings); - } - } else { - if (connection) { - if (restartFields.some(item => data[item] !== config[item])) { - restart = true; - } - - Object.assign(config, data); - await ConnectionConfiguration.update(config); - } + if (restartFields.some(item => data[item] !== config[item])) { + restart = true; } - if (restart) { - vscode.window.showInformationMessage(`Some settings require a restart to take effect. Reload workspace now?`, `Reload`, `No`) - .then(async (value) => { - if (value === `Reload`) { - await vscode.commands.executeCommand(`workbench.action.reloadWindow`); - } - }); + Object.assign(config, data); + await ConnectionConfiguration.update(config); + + if (connection && restart) { + vscode.window.showInformationMessage(`Some settings require a restart to take effect. Reload workspace now?`, `Reload`, `No`) + .then(async (value) => { + if (value === `Reload`) { + await vscode.commands.executeCommand(`workbench.action.reloadWindow`); + } + }); } } }), From fb6adfbfe13c41c221a015cc30b571fecc1c10b0 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 11 Apr 2023 18:26:02 +0200 Subject: [PATCH 11/12] Fix update of current config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Julliand --- src/webviews/settings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index 2fda5d5c0..c01ff7897 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -168,7 +168,7 @@ export class SettingsUI { } Object.assign(config, data); - await ConnectionConfiguration.update(config); + await instance.setConfig(config); if (connection && restart) { vscode.window.showInformationMessage(`Some settings require a restart to take effect. Reload workspace now?`, `Reload`, `No`) From dc67986d7534a3bbfbaef93b5e62d8fa6b6d258a Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 11 Apr 2023 21:28:17 +0200 Subject: [PATCH 12/12] Fix optional parameter in call to 'connect' --- src/webviews/login/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webviews/login/index.ts b/src/webviews/login/index.ts index 6c5572870..824107f98 100644 --- a/src/webviews/login/index.ts +++ b/src/webviews/login/index.ts @@ -143,7 +143,7 @@ export class Login { } try { - const connected = await new IBMi().connect(connectionConfig, reloadServerSettings); + const connected = await new IBMi().connect(connectionConfig, undefined, reloadServerSettings); if (connected.success) { vscode.window.showInformationMessage(`Connected to ${connectionConfig.host}!`); } else {