diff --git a/package.json b/package.json index 13628c52a..9f7bb1a78 100644 --- a/package.json +++ b/package.json @@ -2359,7 +2359,7 @@ }, { "command": "code-for-ibmi.downloadMemberAsFile", - "when": "view == objectBrowser && viewItem =~ /^member.*$/", + "when": "view == objectBrowser && viewItem =~ /^(member|SPF).*$/", "group": "3_memberTransfer@1" }, { diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index e4130ef67..cd012d919 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -1171,4 +1171,25 @@ export default class IBMi { return file.fsPath; } } + + /** + * Creates a temporary directory and pass it on to a `process` function. + * The directory is guaranteed to be empty when created and deleted after the `process` is done. + * @param process the process that will run on the empty directory + */ + async withTempDirectory(process: (directory: string) => Promise) { + const tempDirectory = `${this.config?.tempDir || '/tmp'}/code4itemp${Tools.makeid(20)}`; + const prepareDirectory = await this.sendCommand({ command: `rm -rf ${tempDirectory} && mkdir -p ${tempDirectory}` }); + if (prepareDirectory.code === 0) { + try { + await process(tempDirectory); + } + finally { + await this.sendCommand({ command: `rm -rf ${tempDirectory}` }); + } + } + else { + throw new Error(`Failed to create temporary directory ${tempDirectory}: ${prepareDirectory.stderr}`); + } + } } \ No newline at end of file diff --git a/src/locale/ids/da.ts b/src/locale/ids/da.ts index bcc4c9eae..d683d2ab7 100644 --- a/src/locale/ids/da.ts +++ b/src/locale/ids/da.ts @@ -33,6 +33,11 @@ export const da: Locale = { 'type': 'Type', 'attribute': 'Attribut', 'created_by': 'Oprettet af', + 'overwrite': 'Erstat', + 'overwrite_all': 'Erstat alle', + 'ask.overwrite':'{0} eksisterer allerede.\nVil du erstatte?', + 'skip':'Spring over', + 'skip_all':'Spring alle over', // Sandbox: 'sandbox.input.user.title': `Bruger for server`, 'sandbox.input.user.prompt': `Indtast brugernavn for {0}`, @@ -116,7 +121,6 @@ export const da: Locale = { 'ifsBrowser.searchIFS.title2': `Søg i {0}`, 'ifsBrowser.searchIFS.noGrep': `'grep' skal være installeret på systemet for IFS søgning.`, 'ifsBrowser.downloadStreamfile.downloading':'Henter', - 'ifsBrowser.downloadStreamfile.overwrite':'{0} eksisterer allerede.\nVil du erstatte?', 'ifsBrowser.downloadStreamfile.complete': `Hentning gennemført.`, 'ifsBrowser.downloadStreamfile.errorMessage': `Fejl under hentning: {0}`, 'ifsBrowser.getChildren.errorMessage': `Fejl ved læsning af objekter.`, @@ -166,8 +170,12 @@ export const da: Locale = { 'objectBrowser.renameMember.invalid.input': `Nyt member navn skal være forskelligt fra det nuværende navn`, 'objectBrowser.uploadAndReplaceMemberAsFile.infoMessage': `Fil blev sendt til member.`, 'objectBrowser.uploadAndReplaceMemberAsFile.errorMessage': `Fejl ved afsendelse af fil! {0}`, - 'objectBrowser.downloadMemberContent.infoMessage': `Member blev hentet.`, - 'objectBrowser.downloadMemberContent.errorMessage': `Fejl ved hentning af member! {0}`, + 'objectBrowser.downloadMemberContent.infoMessage': `Members download complete.`, + 'objectBrowser.downloadMemberContent.errorMessage': `Error downloading member(s)! {0}`, + 'objectBrowser.downloadMemberContent.cancel': `Members download cancelled.`, + 'objectBrowser.downloadMemberContent.download.progress':'Downloading {0} members', + 'objectBrowser.downloadMemberContent.download.cpytostmf':'copying to streamfiles', + 'objectBrowser.downloadMemberContent.download.streamfiles':'getting streamfiles', 'objectBrowser.searchSourceFile.prompt': `Indtast LIB/SPF/member.ext til søgning (member.ext er valgfrit og kan indeholde mønstertegn)`, 'objectBrowser.searchSourceFile.title': `Søg source fil`, 'objectBrowser.searchSourceFile.invalidForm': `Venligst indtast værdi på formatet LIB/SPF/member.ext`, diff --git a/src/locale/ids/en.ts b/src/locale/ids/en.ts index b9db3e5c1..175d9ecd6 100644 --- a/src/locale/ids/en.ts +++ b/src/locale/ids/en.ts @@ -33,6 +33,11 @@ export const en: Locale = { 'type': 'Type', 'attribute': 'Attribute', 'created_by': 'Created by', + 'overwrite': 'Overwrite', + 'overwrite_all': 'Overwrite all', + 'ask.overwrite':'{0} already exists.\nDo you want to replace it?', + 'skip':'Skip', + 'skip_all':'Skip all', // Sandbox: 'sandbox.input.user.title': `User for server`, 'sandbox.input.user.prompt': `Enter username for {0}`, @@ -115,8 +120,7 @@ export const en: Locale = { 'ifsBrowser.searchIFS.placeholder': `Enter search term or select one of the previous search terms.`, 'ifsBrowser.searchIFS.placeholder2': `Enter search term.`, 'ifsBrowser.searchIFS.noGrep': `grep must be installed on the remote system for the IFS search.`, - 'ifsBrowser.downloadStreamfile.downloading':'Downloading', - 'ifsBrowser.downloadStreamfile.overwrite':'{0} already exists.\nDo you want to replace it?', + 'ifsBrowser.downloadStreamfile.downloading':'Downloading', 'ifsBrowser.downloadStreamfile.complete': `Download complete`, 'ifsBrowser.downloadStreamfile.errorMessage': `Error downloading file(s): {0}`, 'ifsBrowser.getChildren.errorMessage': `Error loading objects.`, @@ -166,8 +170,12 @@ export const en: Locale = { 'objectBrowser.renameMember.invalid.input': `New member name must be different from it's current name`, 'objectBrowser.uploadAndReplaceMemberAsFile.infoMessage': `Member was uploaded.`, 'objectBrowser.uploadAndReplaceMemberAsFile.errorMessage': `Error uploading content to member! {0}`, - 'objectBrowser.downloadMemberContent.infoMessage': `Member was downloaded.`, - 'objectBrowser.downloadMemberContent.errorMessage': `Error downloading member! {0}`, + 'objectBrowser.downloadMemberContent.infoMessage': `Members download complete.`, + 'objectBrowser.downloadMemberContent.errorMessage': `Error downloading member(s)! {0}`, + 'objectBrowser.downloadMemberContent.cancel': `Members download cancelled.`, + 'objectBrowser.downloadMemberContent.download.progress':'Downloading {0} members', + 'objectBrowser.downloadMemberContent.download.cpytostmf':'copying to streamfiles', + 'objectBrowser.downloadMemberContent.download.streamfiles':'getting streamfiles', 'objectBrowser.searchSourceFile.prompt': `Enter LIB/SPF/member.ext to search (member.ext is optional and can contain wildcards)`, 'objectBrowser.searchSourceFile.title': `Search source file`, 'objectBrowser.searchSourceFile.invalidForm': `Please enter value in form LIB/SPF/member.ext`, diff --git a/src/locale/ids/fr.ts b/src/locale/ids/fr.ts index 0ab9db5de..df7428100 100644 --- a/src/locale/ids/fr.ts +++ b/src/locale/ids/fr.ts @@ -33,6 +33,11 @@ export const fr: Locale = { 'type': 'Type', 'attribute': 'Attribut', 'created_by': 'Créé par', + 'overwrite': 'Écraser', + 'overwrite_all': 'Écraser tout', + 'ask.overwrite':'{0} existe déjà.\nVoulez-vous le remplacer?', + 'skip':'Ignorer', + 'skip_all':'Ignorer tout', // Sandbox: 'sandbox.input.user.title': `Nom d'utilisateur`, 'sandbox.input.user.prompt': `Entrez le nom d'utilisateur pour {0}`, @@ -116,7 +121,6 @@ export const fr: Locale = { 'ifsBrowser.searchIFS.title2': `Chercher dans {0}.`, 'ifsBrowser.searchIFS.noGrep': `grep doit être installé sur le système distant pour la recherche sur l'IFS.`, 'ifsBrowser.downloadStreamfile.downloading':'Téléchargement', - 'ifsBrowser.downloadStreamfile.overwrite':'{0} existe déjà.\nVoulez-vous le remplacer?', 'ifsBrowser.downloadStreamfile.complete': `Téléchargement terminé`, 'ifsBrowser.downloadStreamfile.errorMessage': `Erreur lors du téléchargement: {0}`, 'ifsBrowser.getChildren.errorMessage': `Erreur lors du chargement des objets.`, @@ -166,8 +170,12 @@ export const fr: Locale = { 'objectBrowser.renameMember.invalid.input': `Le nouveau nom du membre doit être différent de l'ancien`, 'objectBrowser.uploadAndReplaceMemberAsFile.infoMessage': `Le membre a été uploadé.`, 'objectBrowser.uploadAndReplaceMemberAsFile.errorMessage': `Erreur lors de l'upload du contenu du membre! {0}`, - 'objectBrowser.downloadMemberContent.infoMessage': `Le membre a été téléchargé.`, - 'objectBrowser.downloadMemberContent.errorMessage': `Erreur lors du téléchargement du membre! {0}`, + 'objectBrowser.downloadMemberContent.infoMessage': `Téléchargement des membres terminé.`, + 'objectBrowser.downloadMemberContent.errorMessage': `Erreur lors du téléchargement des membres! {0}`, + 'objectBrowser.downloadMemberContent.cancel': `Téléchargement des membres annulé.`, + 'objectBrowser.downloadMemberContent.download.progress':'Téléchargement de {0} membres', + 'objectBrowser.downloadMemberContent.download.cpytostmf':'copie des membres en streamfiles', + 'objectBrowser.downloadMemberContent.download.streamfiles':'récupération des streamfiles', 'objectBrowser.searchSourceFile.prompt': `Entrez LIB/SPF/membre.ext pour chercher (membre.ext est optionnel et peut contenir des caractères génériques)`, 'objectBrowser.searchSourceFile.title': `Rechercher dans un fichier source`, 'objectBrowser.searchSourceFile.invalidForm': `Entrez une valeur de la forme LIB/SPF/membre.ext`, diff --git a/src/testing/connection.ts b/src/testing/connection.ts index f7e7d0073..a4ab0e469 100644 --- a/src/testing/connection.ts +++ b/src/testing/connection.ts @@ -1,249 +1,309 @@ import assert from "assert"; -import { commands } from "vscode"; import { TestSuite } from "."; import { instance } from "../instantiate"; -import { CommandResult } from "../typings"; -export const ConnectionSuite: TestSuite = { +export const ConnectionSuite: TestSuite = { name: `Connection tests`, tests: [ - {name: `Test sendCommand`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.sendCommand({ - command: `echo "Hello world"`, - }); - - assert.strictEqual(result?.code, 0); - assert.strictEqual(result?.stdout, `Hello world`); - }}, - - {name: `Test sendCommand home directory`, test: async () => { - const connection = instance.getConnection(); - - const resultA = await connection?.sendCommand({ - command: `pwd`, - directory: `/QSYS.LIB` - }); - - assert.strictEqual(resultA?.code, 0); - assert.strictEqual(resultA?.stdout, `/QSYS.LIB`); - - const resultB = await connection?.sendCommand({ - command: `pwd`, - directory: `/home` - }); - - assert.strictEqual(resultB?.code, 0); - assert.strictEqual(resultB?.stdout, `/home`); - - const resultC = await connection?.sendCommand({ - command: `pwd`, - directory: `/badnaughty` - }); - - assert.notStrictEqual(resultC?.stdout, `/badnaughty`); - }}, - - {name: `Test sendCommand with environment variables`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.sendCommand({ - command: `echo "$vara $varB $VARC"`, - env: { - vara: `Hello`, - varB: `world`, - VARC: `cool` + { + name: `Test sendCommand`, test: async () => { + const connection = instance.getConnection(); + + const result = await connection?.sendCommand({ + command: `echo "Hello world"`, + }); + + assert.strictEqual(result?.code, 0); + assert.strictEqual(result?.stdout, `Hello world`); + } + }, + + { + name: `Test sendCommand home directory`, test: async () => { + const connection = instance.getConnection(); + + const resultA = await connection?.sendCommand({ + command: `pwd`, + directory: `/QSYS.LIB` + }); + + assert.strictEqual(resultA?.code, 0); + assert.strictEqual(resultA?.stdout, `/QSYS.LIB`); + + const resultB = await connection?.sendCommand({ + command: `pwd`, + directory: `/home` + }); + + assert.strictEqual(resultB?.code, 0); + assert.strictEqual(resultB?.stdout, `/home`); + + const resultC = await connection?.sendCommand({ + command: `pwd`, + directory: `/badnaughty` + }); + + assert.notStrictEqual(resultC?.stdout, `/badnaughty`); + } + }, + + { + name: `Test sendCommand with environment variables`, test: async () => { + const connection = instance.getConnection(); + + const result = await connection?.sendCommand({ + command: `echo "$vara $varB $VARC"`, + env: { + vara: `Hello`, + varB: `world`, + VARC: `cool` + } + }); + + assert.strictEqual(result?.code, 0); + assert.strictEqual(result?.stdout, `Hello world cool`); + } + }, + + { + name: `Test getTempRemote`, test: async () => { + const connection = instance.getConnection(); + + const fileA = connection?.getTempRemote(`/some/file`); + const fileB = connection?.getTempRemote(`/some/badfile`); + const fileC = connection?.getTempRemote(`/some/file`); + + assert.strictEqual(fileA, fileC); + assert.notStrictEqual(fileA, fileB); + } + }, + + { + name: `Test parserMemberPath (simple)`, test: async () => { + const connection = instance.getConnection(); + + const memberA = connection?.parserMemberPath(`/thelib/thespf/thembr.mbr`); + + assert.strictEqual(memberA?.asp, undefined); + assert.strictEqual(memberA?.library, `THELIB`); + assert.strictEqual(memberA?.file, `THESPF`); + assert.strictEqual(memberA?.name, `THEMBR`); + assert.strictEqual(memberA?.extension, `MBR`); + assert.strictEqual(memberA?.basename, `THEMBR.MBR`); + } + }, + + { + name: `Test parserMemberPath (ASP)`, test: async () => { + const connection = instance.getConnection(); + + const memberA = connection?.parserMemberPath(`/theasp/thelib/thespf/thembr.mbr`); + + assert.strictEqual(memberA?.asp, `THEASP`); + assert.strictEqual(memberA?.library, `THELIB`); + assert.strictEqual(memberA?.file, `THESPF`); + assert.strictEqual(memberA?.name, `THEMBR`); + assert.strictEqual(memberA?.extension, `MBR`); + assert.strictEqual(memberA?.basename, `THEMBR.MBR`); + } + }, + + { + name: `Test parserMemberPath (no root)`, test: async () => { + const connection = instance.getConnection(); + + const memberA = connection?.parserMemberPath(`thelib/thespf/thembr.mbr`); + + assert.strictEqual(memberA?.asp, undefined); + assert.strictEqual(memberA?.library, `THELIB`); + assert.strictEqual(memberA?.file, `THESPF`); + assert.strictEqual(memberA?.name, `THEMBR`); + assert.strictEqual(memberA?.extension, `MBR`); + assert.strictEqual(memberA?.basename, `THEMBR.MBR`); + } + }, + + { + name: `Test parserMemberPath (no extension)`, test: async () => { + const connection = instance.getConnection(); + + try { + const memberA = connection?.parserMemberPath(`thelib/thespf/thembr`); + } catch (e: any) { + assert.strictEqual(e.message, `Source Type extension is required.`); + } + } + }, + + { + name: `Test parserMemberPath (invalid length)`, test: async () => { + const connection = instance.getConnection(); + + try { + const memberA = connection?.parserMemberPath(`/thespf/thembr.mbr`); + } catch (e: any) { + assert.strictEqual(e.message, `Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); } - }); - - assert.strictEqual(result?.code, 0); - assert.strictEqual(result?.stdout, `Hello world cool`); - }}, - - {name: `Test getTempRemote`, test: async () => { - const connection = instance.getConnection(); - - const fileA = connection?.getTempRemote(`/some/file`); - const fileB = connection?.getTempRemote(`/some/badfile`); - const fileC = connection?.getTempRemote(`/some/file`); - - assert.strictEqual(fileA, fileC); - assert.notStrictEqual(fileA, fileB); - }}, - - {name: `Test parserMemberPath (simple)`, test: async () => { - const connection = instance.getConnection(); - - const memberA = connection?.parserMemberPath(`/thelib/thespf/thembr.mbr`); - - assert.strictEqual(memberA?.asp, undefined); - assert.strictEqual(memberA?.library, `THELIB`); - assert.strictEqual(memberA?.file, `THESPF`); - assert.strictEqual(memberA?.name, `THEMBR`); - assert.strictEqual(memberA?.extension, `MBR`); - assert.strictEqual(memberA?.basename, `THEMBR.MBR`); - }}, - - {name: `Test parserMemberPath (ASP)`, test: async () => { - const connection = instance.getConnection(); - - const memberA = connection?.parserMemberPath(`/theasp/thelib/thespf/thembr.mbr`); - - assert.strictEqual(memberA?.asp, `THEASP`); - assert.strictEqual(memberA?.library, `THELIB`); - assert.strictEqual(memberA?.file, `THESPF`); - assert.strictEqual(memberA?.name, `THEMBR`); - assert.strictEqual(memberA?.extension, `MBR`); - assert.strictEqual(memberA?.basename, `THEMBR.MBR`); - }}, - - {name: `Test parserMemberPath (no root)`, test: async () => { - const connection = instance.getConnection(); - - const memberA = connection?.parserMemberPath(`thelib/thespf/thembr.mbr`); - - assert.strictEqual(memberA?.asp, undefined); - assert.strictEqual(memberA?.library, `THELIB`); - assert.strictEqual(memberA?.file, `THESPF`); - assert.strictEqual(memberA?.name, `THEMBR`); - assert.strictEqual(memberA?.extension, `MBR`); - assert.strictEqual(memberA?.basename, `THEMBR.MBR`); - }}, - - {name: `Test parserMemberPath (no extension)`, test: async () => { - const connection = instance.getConnection(); - - try { - const memberA = connection?.parserMemberPath(`thelib/thespf/thembr`); - } catch (e: any) { - assert.strictEqual(e.message, `Source Type extension is required.`); } - }}, - - {name: `Test parserMemberPath (invalid length)`, test: async () => { - const connection = instance.getConnection(); - - try { - const memberA = connection?.parserMemberPath(`/thespf/thembr.mbr`); - } catch (e: any) { - assert.strictEqual(e.message, `Invalid path: /thespf/thembr.mbr. Use format LIB/SPF/NAME.ext`); + }, + + { + name: `Test runCommand (ILE)`, test: async () => { + const connection = instance.getConnection(); + + const result = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile` + }); + + assert.strictEqual(result?.code, 0); + assert.strictEqual(result.stdout.includes(`Library List`), true); } - }}, - - {name: `Test runCommand (ILE)`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile` - }); - - assert.strictEqual(result?.code, 0); - assert.strictEqual(result.stdout.includes(`Library List`), true); - }}, - - {name: `Test runCommand (with error)`, test: async () => { - const connection = instance.getConnection(); - - // One day it'd be cool to test different locales/CCSIDs here - // const profileMatix = [{ccsid: 277, language: `DAN`, region: `DK`}]; - // for (const setup of profileMatix) { + }, + + { + name: `Test runCommand (with error)`, test: async () => { + const connection = instance.getConnection(); + + // One day it'd be cool to test different locales/CCSIDs here + // const profileMatix = [{ccsid: 277, language: `DAN`, region: `DK`}]; + // for (const setup of profileMatix) { // const profileChange = await connection?.runCommand({ // command: `CHGUSRPRF USRPRF(${connection.currentUser}) CCSID(${setup.ccsid}) LANGID(${setup.language}) CNTRYID(${setup.region})`, // noLibList: true // }); - + // console.log(profileChange); // assert.strictEqual(profileChange?.code, 0); - // } - - console.log((await connection?.runCommand({command: `DSPUSRPRF USRPRF(${connection.currentUser}) OUTPUT(*PRINT)`, noLibList: true}))?.stdout); - - const result = await connection?.runCommand({ - command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, - noLibList: true - }); - - assert.notStrictEqual(result?.code, 0); - assert.ok(result?.stderr); - }}, - - {name: `Test runCommand (ILE, custom libl)`, test: async () => { - const connection = instance.getConnection(); - const config = instance.getConfig(); - - const ogLibl = config!.libraryList.slice(0); - - config!.libraryList = [`QTEMP`]; - - const resultA = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile` - }); - - config!.libraryList = ogLibl; - - assert.strictEqual(resultA?.code, 0); - assert.strictEqual(resultA.stdout.includes(`QSYSINC CUR`), false); - assert.strictEqual(resultA.stdout.includes(`QSYSINC USR`), false); - - const resultB = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile`, - env: { - '&LIBL': `QSYSINC`, - '&CURLIB': `QSYSINC` - } - }); - - assert.strictEqual(resultB?.code, 0); - assert.strictEqual(resultB.stdout.includes(`QSYSINC CUR`), true); - assert.strictEqual(resultB.stdout.includes(`QSYSINC USR`), true); - }}, - - {name: `Test runCommand (ILE, libl order from variable)`, test: async () => { - const connection = instance.getConnection(); - - const result = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile`, - env: { - '&LIBL': `QTEMP QSYSINC`, - } - }); + // } + + console.log((await connection?.runCommand({ command: `DSPUSRPRF USRPRF(${connection.currentUser}) OUTPUT(*PRINT)`, noLibList: true }))?.stdout); + + const result = await connection?.runCommand({ + command: `CHKOBJ OBJ(QSYS/NOEXIST) OBJTYPE(*DTAARA)`, + noLibList: true + }); + + assert.notStrictEqual(result?.code, 0); + assert.ok(result?.stderr); + } + }, + + { + name: `Test runCommand (ILE, custom libl)`, test: async () => { + const connection = instance.getConnection(); + const config = instance.getConfig(); + + const ogLibl = config!.libraryList.slice(0); + + config!.libraryList = [`QTEMP`]; + + const resultA = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile` + }); + + config!.libraryList = ogLibl; - assert.strictEqual(result?.code, 0); + assert.strictEqual(resultA?.code, 0); + assert.strictEqual(resultA.stdout.includes(`QSYSINC CUR`), false); + assert.strictEqual(resultA.stdout.includes(`QSYSINC USR`), false); - const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); - const qtempIndex = result.stdout.indexOf(`QTEMP USR`); - - // Test that QSYSINC is before QSYS2 - assert.ok(qtempIndex < qsysincIndex); - }}, + const resultB = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile`, + env: { + '&LIBL': `QSYSINC`, + '&CURLIB': `QSYSINC` + } + }); - {name: `Test runCommand (ILE, libl order from config)`, test: async () => { - const connection = instance.getConnection(); - const config = instance.getConfig(); + assert.strictEqual(resultB?.code, 0); + assert.strictEqual(resultB.stdout.includes(`QSYSINC CUR`), true); + assert.strictEqual(resultB.stdout.includes(`QSYSINC USR`), true); + } + }, + + { + name: `Test runCommand (ILE, libl order from variable)`, test: async () => { + const connection = instance.getConnection(); + + const result = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile`, + env: { + '&LIBL': `QTEMP QSYSINC`, + } + }); + + assert.strictEqual(result?.code, 0); + + const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); + const qtempIndex = result.stdout.indexOf(`QTEMP USR`); + + // Test that QSYSINC is before QSYS2 + assert.ok(qtempIndex < qsysincIndex); + } + }, + + { + name: `Test runCommand (ILE, libl order from config)`, test: async () => { + const connection = instance.getConnection(); + const config = instance.getConfig(); + + const ogLibl = config!.libraryList.slice(0); - const ogLibl = config!.libraryList.slice(0); - - config!.libraryList = [`QTEMP`, `QSYSINC`]; + config!.libraryList = [`QTEMP`, `QSYSINC`]; - const result = await connection?.runCommand({ - command: `DSPLIBL`, - environment: `ile`, - }); + const result = await connection?.runCommand({ + command: `DSPLIBL`, + environment: `ile`, + }); - config!.libraryList = ogLibl; + config!.libraryList = ogLibl; - assert.strictEqual(result?.code, 0); + assert.strictEqual(result?.code, 0); - const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); - const qtempIndex = result.stdout.indexOf(`QTEMP USR`); - - // Test that QSYSINC is before QSYS2 - assert.ok(qtempIndex < qsysincIndex); - }}, + const qsysincIndex = result.stdout.indexOf(`QSYSINC USR`); + const qtempIndex = result.stdout.indexOf(`QTEMP USR`); + + // Test that QSYSINC is before QSYS2 + assert.ok(qtempIndex < qsysincIndex); + } + }, + { + name: `Test withTempDirectory`, test: async () => { + const connection = instance.getConnection()!; + let temp; + const countFiles = async(dir:string) => { + const countResult = await connection.sendCommand({command: `find ${dir} -type f | wc -l`}); + assert.strictEqual(countResult.code, 0); + return Number(countResult.stdout); + }; + + await connection.withTempDirectory(async tempDir => { + temp = tempDir; + //Directory must exist + assert.strictEqual((await connection.sendCommand({command: `[ -d ${tempDir} ]`})).code, 0); + + //Directory must be empty + assert.strictEqual(await countFiles(tempDir), 0); + + const toCreate = 10; + for(let i = 0; i < toCreate; i++){ + assert.strictEqual((await connection.sendCommand({command: `echo "Test ${i}" >> ${tempDir}/file${i}`})).code, 0); + } + + const newCountResult = await connection.sendCommand({command: `ls -l ${tempDir} | wc -l`}); + assert.strictEqual(newCountResult.code, 0); + assert.strictEqual(await countFiles(tempDir), toCreate); + }); + + if(temp){ + //Directory must be gone + assert.strictEqual((await connection.sendCommand({command: `[ -d ${temp} ]`})).code, 1); + } + } + } ] }; diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index 10fe5d0d2..81e62d190 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -777,7 +777,7 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { } } else { - if (!existsSync(target) || await vscode.window.showWarningMessage(t('ifsBrowser.downloadStreamfile.overwrite', target), { modal: true }, t("Yes"))) { + if (!existsSync(target) || await vscode.window.showWarningMessage(t('ask.overwrite', target), { modal: true }, t("Yes"))) { await ibmi.downloadFile(target, targetPath); } } diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 6eb145fe0..0c5add4b9 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -1,6 +1,6 @@ -import fs from "fs"; +import fs, { existsSync } from "fs"; import os from "os"; -import path, { dirname } from "path"; +import path, { basename, dirname } from "path"; import util from "util"; import vscode from "vscode"; import { ConnectionConfiguration, DefaultOpenMode, GlobalConfiguration } from "../api/Configuration"; @@ -789,23 +789,102 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { }), - vscode.commands.registerCommand(`code-for-ibmi.downloadMemberAsFile`, async (node: MemberItem) => { + vscode.commands.registerCommand(`code-for-ibmi.downloadMemberAsFile`, async (node: BrowserItem, nodes?: BrowserItem[]) => { const contentApi = getContent(); const connection = getConnection(); + const config = getConfig(); + + //Gather all the members + const members: IBMiMember[] = []; + for (const item of (nodes || [node])) { + if (item instanceof ObjectBrowserSourcePhysicalFileItem) { + members.push(...await contentApi.getMemberList({ library: item.object.library, sourceFile: item.object.name })); + } + else if(item instanceof ObjectBrowserMemberItem) { + members.push(item.member); + } + } + + const saveIntoDirectory = members.length > 1; + let downloadLocation: string | undefined; + if (saveIntoDirectory) { + downloadLocation = (await vscode.window.showOpenDialog({ + canSelectMany: false, + canSelectFiles: false, + canSelectFolders: true, + defaultUri: vscode.Uri.file(connection.getLastDownloadLocation()) + }))?.[0]?.path; + } + else { + downloadLocation = (await vscode.window.showSaveDialog({ + defaultUri: vscode.Uri.file(path.join(connection.getLastDownloadLocation(), members[0].name)), + filters: { 'Source member': [members[0].extension || '*'] } + }))?.path; + } - const { asp, library, file, name: member, basename } = connection.parserMemberPath(node.path); + if (downloadLocation) { + //Remove double entries and map to { path, copy } object + const toBeDownloaded = members + .filter((member, index, list) => list.findIndex(m => m.library === member.library && m.file === member.file && m.name === member.name) === index) + .sort((m1, m2) => m1.name.localeCompare(m2.name)) + .map(member => ({ path: Tools.qualifyPath(member.library, member.file, member.name, member.asp), name: `${member.name}.${member.extension || "MBR"}`, copy: true })); - const memberContent = await contentApi.downloadMemberContent(asp, library, file, member); + if (!saveIntoDirectory) { + toBeDownloaded[0].name = basename(downloadLocation); + downloadLocation = dirname(downloadLocation); + } - const localFilepath = (await vscode.window.showSaveDialog({ defaultUri: vscode.Uri.file(path.join(connection.getLastDownloadLocation(), basename)) }))?.path; - if (localFilepath) { - await connection.setLastDownloadLocation(dirname(localFilepath)); - try { - await writeFileAsync(Tools.fixWindowsPath(localFilepath), memberContent, `utf8`); - vscode.window.showInformationMessage(t(`objectBrowser.downloadMemberContent.infoMessage`)); - } catch (e) { - vscode.window.showErrorMessage(t(`objectBrowser.downloadMemberContent.errorMessage`, e)); + await connection.setLastDownloadLocation(downloadLocation); + + //Ask what do to with existing files in the target directory + if (saveIntoDirectory) { + let overwriteAll = false; + let skipAll = false; + const overwriteLabel = t('overwrite'); + const overwriteAllLabel = t('overwrite_all'); + const skipAllLabel = t('skip_all'); + for (const item of toBeDownloaded) { + const target = path.join(Tools.fixWindowsPath(downloadLocation), item.name); + if (existsSync(target)) { + if (skipAll) { + item.copy = false; + } + else if (!overwriteAll) { + const answer = await vscode.window.showWarningMessage(t('ask.overwrite', item.name), { modal: true }, t('skip'), skipAllLabel, overwriteLabel, overwriteAllLabel); + if (answer) { + overwriteAll ||= (answer === overwriteAllLabel); + skipAll ||= (answer === skipAllLabel); + item.copy = !skipAll && (overwriteAll || answer === overwriteLabel); + } + else { + //Abort! + vscode.window.showInformationMessage(t('objectBrowser.downloadMemberContent.cancel')); + return; + } + } + } + } } + + //Download members + vscode.window.withProgress({ title: t('objectBrowser.downloadMemberContent.download.progress', toBeDownloaded.filter(m => m.copy).length), location: vscode.ProgressLocation.Notification }, async (task) => { + try { + await connection.withTempDirectory(async directory => { + task.report({ message: t('objectBrowser.downloadMemberContent.download.cpytostmf'), increment: 33 }) + const copyToStreamFiles = toBeDownloaded + .filter(member => member.copy) + .map(member => `@CPYTOSTMF FROMMBR('${member.path}') TOSTMF('${directory}/${member.name.toLocaleLowerCase()}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${config.sourceFileCCSID}) ENDLINFMT(*LF);`) + .join("\n"); + await contentApi.runSQL(copyToStreamFiles); + + task.report({ message: t('objectBrowser.downloadMemberContent.download.streamfiles'), increment: 33 }) + await connection.downloadDirectory(downloadLocation!, directory); + vscode.window.showInformationMessage(t(`objectBrowser.downloadMemberContent.infoMessage`)); + }); + } catch (e) { + vscode.window.showErrorMessage(t(`objectBrowser.downloadMemberContent.errorMessage`, e)); + } + }); } }),