diff --git a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerAbstractFtpApi.ts b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerAbstractFtpApi.ts index 9b168c8079..f25f04399f 100644 --- a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerAbstractFtpApi.ts +++ b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerAbstractFtpApi.ts @@ -66,4 +66,11 @@ export abstract class AbstractFtpApi implements ZoweExplorerApi.ICommon { secureFtp: ftpProfile.secureFtp, }); } + public releaseConnection(connection: any): void { + if (connection != null) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + connection.close(); + return; + } + } } diff --git a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpJesApi.ts b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpJesApi.ts index b10e879092..9f0b582b95 100644 --- a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpJesApi.ts +++ b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpJesApi.ts @@ -20,143 +20,165 @@ import { AbstractFtpApi } from "./ZoweExplorerAbstractFtpApi"; // The Zowe FTP CLI plugin is written and uses mostly JavaScript, so relax the rules here. /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unused-vars*/ export class FtpJesApi extends AbstractFtpApi implements ZoweExplorerApi.IJes { public async getJobsByOwnerAndPrefix(owner: string, prefix: string): Promise { const result = this.getIJobResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const options = { - owner: owner, - }; - const response = await JobUtils.listJobs(connection, prefix, options); - if (response) { - const results = response.map((job: IJob) => { - return { - ...result, - /* it’s prepared for the potential change in zftp api, renaming jobid to jobId, jobname to jobName. */ - jobid: (job as any).jobId || job.jobid, - jobname: (job as any).jobName || job.jobname, - owner: job.owner, - class: job.class, - status: job.status, - }; - }); - return results; + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const options = { + owner: owner, + }; + const response = await JobUtils.listJobs(connection, prefix, options); + if (response) { + const results = response.map((job: IJob) => { + return { + ...result, + /* it’s prepared for the potential change in zftp api, renaming jobid to jobId, jobname to jobName. */ + jobid: (job as any).jobId || job.jobid, + jobname: (job as any).jobName || job.jobname, + owner: job.owner, + class: job.class, + status: job.status, + }; + }); + return results; + } } + return [result]; + } finally { + this.releaseConnection(connection); } - return [result]; } public async getJob(jobid: string): Promise { const result = this.getIJobResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const jobStatus: IJobStatus = await JobUtils.findJobByID(connection, jobid); - if (jobStatus) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return { - ...result, - /* it’s prepared for the potential change in zftp api, renaming jobid to jobId, jobname to jobName. */ - jobid: (jobStatus as any).jobId || jobStatus.jobid, - jobname: (jobStatus as any).jobName || jobStatus.jobname, - owner: jobStatus.owner, - class: jobStatus.class, - status: jobStatus.status, - }; + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const jobStatus: IJobStatus = await JobUtils.findJobByID(connection, jobid); + if (jobStatus) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + ...result, + /* it’s prepared for the potential change in zftp api, renaming jobid to jobId, jobname to jobName. */ + jobid: (jobStatus as any).jobId || jobStatus.jobid, + jobname: (jobStatus as any).jobName || jobStatus.jobname, + owner: jobStatus.owner, + class: jobStatus.class, + status: jobStatus.status, + }; + } } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async getSpoolFiles(jobname: string, jobid: string): Promise { const result = this.getIJobFileResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const response: IJobStatus = await JobUtils.findJobByID(connection, jobid); - const files: any = response.spoolFiles; - if (files) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return files.map((file: ISpoolFile) => { - return { - /* it’s prepared for the potential change in zftp api, renaming stepname to stepName, procstep to procStep, ddname to ddName. */ - jobid: jobid, - jobname: jobname, - "byte-count": file.byteCount, - id: file.id, - stepname: (file as any).stepName || file.stepname, - procstep: (file as any).procStep || file.procstep, - class: file.class, - ddname: (file as any).ddName || file.ddname, - }; - }); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const response: IJobStatus = await JobUtils.findJobByID(connection, jobid); + const files: any = response.spoolFiles; + if (files) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call + return files.map((file: ISpoolFile) => { + return { + /* it’s prepared for the potential change in zftp api, renaming stepname to stepName, procstep to procStep, ddname to ddName. */ + jobid: jobid, + jobname: jobname, + "byte-count": file.byteCount, + id: file.id, + stepname: (file as any).stepName || file.stepname, + procstep: (file as any).procStep || file.procstep, + class: file.class, + ddname: (file as any).ddName || file.ddname, + }; + }); + } } + return [result]; + } finally { + this.releaseConnection(connection); } - return [result]; } public async downloadSpoolContent(parms: zowe.IDownloadAllSpoolContentParms): Promise { - const connection = await this.ftpClient(this.checkedProfile()); - /* it's duplicate code with zftp. We may add new job API in the next zftp to cover spool file downloading. */ - if (connection) { - const destination = parms.outDir == null ? "./output/" : parms.outDir; - const jobDetails = await JobUtils.findJobByID(connection, parms.jobid); - if (jobDetails.spoolFiles == null || jobDetails.spoolFiles.length === 0) { - throw new Error("No spool files were available."); - } - const fullSpoolFiles = await JobUtils.getSpoolFiles(connection, jobDetails.jobid); - for (const spoolFileToDownload of fullSpoolFiles) { - const mockJobFile: IJobFile = { - // mock a job file to get the same format of download directories - jobid: jobDetails.jobid, - jobname: jobDetails.jobname, - recfm: "FB", - lrecl: 80, - "byte-count": Number(spoolFileToDownload.byteCount), - // todo is recfm or lrecl available? FB 80 could be wrong - "record-count": 0, - "job-correlator": "", // most of these options don't matter for download - class: "A", - ddname: String(spoolFileToDownload.ddname), - id: Number(spoolFileToDownload.id), - "records-url": "", - subsystem: "JES2", - stepname: String(spoolFileToDownload.stepname), - procstep: String( - spoolFileToDownload.procstep === "N/A" || spoolFileToDownload.procstep == null - ? undefined - : spoolFileToDownload.procstep - ), - }; - const destinationFile = DownloadJobs.getSpoolDownloadFile( - mockJobFile, - parms.omitJobidDirectory, - parms.outDir - ); - imperative.IO.createDirsSyncFromFilePath(destinationFile); - imperative.IO.writeFile(destinationFile, spoolFileToDownload.contents as Buffer); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + /* it's duplicate code with zftp. We may add new job API in the next zftp to cover spool file downloading. */ + if (connection) { + const destination = parms.outDir == null ? "./output/" : parms.outDir; + const jobDetails = await JobUtils.findJobByID(connection, parms.jobid); + if (jobDetails.spoolFiles == null || jobDetails.spoolFiles.length === 0) { + throw new Error("No spool files were available."); + } + const fullSpoolFiles = await JobUtils.getSpoolFiles(connection, jobDetails.jobid); + for (const spoolFileToDownload of fullSpoolFiles) { + const mockJobFile: IJobFile = { + // mock a job file to get the same format of download directories + jobid: jobDetails.jobid, + jobname: jobDetails.jobname, + recfm: "FB", + lrecl: 80, + "byte-count": Number(spoolFileToDownload.byteCount), + // todo is recfm or lrecl available? FB 80 could be wrong + "record-count": 0, + "job-correlator": "", // most of these options don't matter for download + class: "A", + ddname: String(spoolFileToDownload.ddname), + id: Number(spoolFileToDownload.id), + "records-url": "", + subsystem: "JES2", + stepname: String(spoolFileToDownload.stepname), + procstep: String( + spoolFileToDownload.procstep === "N/A" || spoolFileToDownload.procstep == null + ? undefined + : spoolFileToDownload.procstep + ), + }; + const destinationFile = DownloadJobs.getSpoolDownloadFile( + mockJobFile, + parms.omitJobidDirectory, + parms.outDir + ); + imperative.IO.createDirsSyncFromFilePath(destinationFile); + imperative.IO.writeFile(destinationFile, spoolFileToDownload.contents as Buffer); + } } + } finally { + this.releaseConnection(connection); } } public async getSpoolContentById(jobname: string, jobid: string, spoolId: number): Promise { - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const options = { - fileId: spoolId.toString(), - jobName: jobname, - jobId: jobid, - owner: "*", - }; - const response: Buffer = await JobUtils.getSpoolFileContent(connection, options); - if (response) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.toString(); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const options = { + fileId: spoolId.toString(), + jobName: jobname, + jobId: jobid, + owner: "*", + }; + const response: Buffer = await JobUtils.getSpoolFileContent(connection, options); + if (response) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return response.toString(); + } } + return ""; + } finally { + this.releaseConnection(connection); } - return ""; } public getJclForJob(job: zowe.IJob): Promise { @@ -169,24 +191,34 @@ export class FtpJesApi extends AbstractFtpApi implements ZoweExplorerApi.IJes { public async submitJob(jobDataSet: string): Promise { const result = this.getIJobResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const transferOptions = { - transferType: TRANSFER_TYPE_ASCII, - }; - const content = await DataSetUtils.downloadDataSet(connection, jobDataSet, transferOptions); - const jcl = content.toString(); - const jobId: string = await JobUtils.submitJob(connection, jcl); - if (jobId) { - result.jobid = jobId; + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const transferOptions = { + transferType: TRANSFER_TYPE_ASCII, + }; + const content = await DataSetUtils.downloadDataSet(connection, jobDataSet, transferOptions); + const jcl = content.toString(); + const jobId: string = await JobUtils.submitJob(connection, jcl); + if (jobId) { + result.jobid = jobId; + } } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async deleteJob(jobname: string, jobid: string): Promise { - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - await JobUtils.deleteJob(connection, jobid); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + await JobUtils.deleteJob(connection, jobid); + } + } finally { + this.releaseConnection(connection); } } diff --git a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts index 27fd141786..cbc81aa389 100644 --- a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts +++ b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpMvsApi.ts @@ -26,61 +26,76 @@ import { AbstractFtpApi } from "./ZoweExplorerAbstractFtpApi"; export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { public async dataSet(filter: string, options?: zowe.IListOptions): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const response: any[] = await DataSetUtils.listDataSets(connection, filter); - if (response) { - result.success = true; - result.apiResponse.items = response.map((element) => ({ - dsname: element.dsname, - dsorg: element.dsorg, - volume: element.volume, - recfm: element.recfm, - blksz: element.blksz, - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - migr: element.volume && element.volume.toUpperCase() === "MIGRATED" ? "YES" : "NO", - })); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const response: any[] = await DataSetUtils.listDataSets(connection, filter); + if (response) { + result.success = true; + result.apiResponse.items = response.map((element) => ({ + dsname: element.dsname, + dsorg: element.dsorg, + volume: element.volume, + recfm: element.recfm, + blksz: element.blksz, + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + migr: element.volume && element.volume.toUpperCase() === "MIGRATED" ? "YES" : "NO", + })); + } } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async allMembers(dataSetName: string, options?: zowe.IListOptions): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const response: any[] = await DataSetUtils.listMembers(connection, dataSetName); - if (response) { - result.success = true; - result.apiResponse.items = response.map((element) => ({ - member: element.name, - changed: element.changed, - created: element.created, - id: element.id, - })); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const response: any[] = await DataSetUtils.listMembers(connection, dataSetName); + if (response) { + result.success = true; + result.apiResponse.items = response.map((element) => ({ + member: element.name, + changed: element.changed, + created: element.created, + id: element.id, + })); + } } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async getContents(dataSetName: string, options: zowe.IDownloadOptions): Promise { - const connection = await this.ftpClient(this.checkedProfile()); const result = this.getDefaultResponse(); const targetFile = options.file; const transferOptions = { transferType: options.binary ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII, localFile: targetFile, }; - if (connection && targetFile) { - imperative.IO.createDirsSyncFromFilePath(targetFile); - await DataSetUtils.downloadDataSet(connection, dataSetName, transferOptions); - result.success = true; - result.commandResponse = ""; - result.apiResponse.etag = await this.hashFile(targetFile); - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection && targetFile) { + imperative.IO.createDirsSyncFromFilePath(targetFile); + await DataSetUtils.downloadDataSet(connection, dataSetName, transferOptions); + result.success = true; + result.commandResponse = ""; + result.apiResponse.etag = await this.hashFile(targetFile); + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async putContents( @@ -92,7 +107,6 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { transferType: options.binary ? TRANSFER_TYPE_BINARY : TRANSFER_TYPE_ASCII, localFile: inputFilePath, }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call const file = path.basename(inputFilePath).replace(/[^a-z0-9]+/gi, ""); const member = file.substr(0, 8); let targetDataset; @@ -104,30 +118,35 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { targetDataset = dataSetName + "(" + member + ")"; } const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (!connection) { - throw new Error(result.commandResponse); - } - // Save-Save with FTP requires loading the file first - if (options.returnEtag && options.etag) { - const contentsTag = await this.getContentsTag(dataSetName); - if (contentsTag && contentsTag !== options.etag) { - // TODO: extension.ts should not check for zosmf errors. - throw new Error("Save conflict. Please pull the latest content from mainfram first."); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (!connection) { + throw new Error(result.commandResponse); } + // Save-Save with FTP requires loading the file first + if (options.returnEtag && options.etag) { + const contentsTag = await this.getContentsTag(dataSetName); + if (contentsTag && contentsTag !== options.etag) { + // TODO: extension.ts should not check for zosmf errors. + throw new Error("Save conflict. Please pull the latest content from mainfram first."); + } + } + await DataSetUtils.uploadDataSet(connection, targetDataset, transferOptions); + result.success = true; + if (options.returnEtag) { + const contentsTag = await this.getContentsTag(dataSetName); + result.apiResponse = [ + { + etag: contentsTag, + }, + ]; + } + result.commandResponse = "Data set uploaded successfully."; + return result; + } finally { + this.releaseConnection(connection); } - await DataSetUtils.uploadDataSet(connection, targetDataset, transferOptions); - result.success = true; - if (options.returnEtag) { - const contentsTag = await this.getContentsTag(dataSetName); - result.apiResponse = [ - { - etag: contentsTag, - }, - ]; - } - result.commandResponse = "Data set uploaded successfully."; - return result; } public async createDataSet( @@ -136,45 +155,54 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { options?: Partial ): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - /* eslint-disable @typescript-eslint/restrict-plus-operands */ const dcbList = []; if (options?.alcunit) { dcbList.push("ALCUNIT=" + options.alcunit); } if (options?.blksize) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands dcbList.push("BLKSIZE=" + options.blksize); } if (options?.dirblk) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands dcbList.push("DIRECTORY=" + options.dirblk); } if (options?.dsorg) { dcbList.push("DSORG=" + options.dsorg); } if (options?.lrecl) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands dcbList.push("LRECL=" + options.lrecl); } if (options?.primary) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands dcbList.push("PRIMARY=" + options.primary); } if (options?.recfm) { dcbList.push("RECFM=" + options.recfm); } if (options?.secondary) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands dcbList.push("SECONDARY=" + options.secondary); } const dcb = dcbList.join(" "); const allocateOptions = { dcb: dcb, }; - if (connection) { - await DataSetUtils.allocateDataSet(connection, dataSetName, allocateOptions); - result.success = true; - result.commandResponse = "Data set created successfully."; - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + await DataSetUtils.allocateDataSet(connection, dataSetName, allocateOptions); + result.success = true; + result.commandResponse = "Data set created successfully."; + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async createDataSetMember( @@ -186,15 +214,20 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { content: "", }; const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (!connection) { - throw new Error(result.commandResponse); - } + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (!connection) { + throw new Error(result.commandResponse); + } - await DataSetUtils.uploadDataSet(connection, dataSetName, transferOptions); - result.success = true; - result.commandResponse = "Member created successfully."; - return result; + await DataSetUtils.uploadDataSet(connection, dataSetName, transferOptions); + result.success = true; + result.commandResponse = "Member created successfully."; + return result; + } finally { + this.releaseConnection(connection); + } } public allocateLikeDataSet(dataSetName: string, likeDataSetName: string): Promise { @@ -211,15 +244,20 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { public async renameDataSet(currentDataSetName: string, newDataSetName: string): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - await DataSetUtils.renameDataSet(connection, currentDataSetName, newDataSetName); - result.success = true; - result.commandResponse = "Rename completed successfully."; - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + await DataSetUtils.renameDataSet(connection, currentDataSetName, newDataSetName); + result.success = true; + result.commandResponse = "Rename completed successfully."; + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async renameDataSetMember( @@ -228,17 +266,22 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { newMemberName: string ): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); const currentName = dataSetName + "(" + currentMemberName + ")"; const newName = dataSetName + "(" + newMemberName + ")"; - if (connection) { - await DataSetUtils.renameDataSet(connection, currentName, newName); - result.success = true; - result.commandResponse = "Rename completed successfully."; - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + await DataSetUtils.renameDataSet(connection, currentName, newName); + result.success = true; + result.commandResponse = "Rename completed successfully."; + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public hMigrateDataSet(dataSetName: string): Promise { @@ -253,15 +296,20 @@ export class FtpMvsApi extends AbstractFtpApi implements ZoweExplorerApi.IMvs { options?: zowe.IDeleteDatasetOptions ): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - await DataSetUtils.deleteDataSet(connection, dataSetName); - result.success = true; - result.commandResponse = "Delete completed successfully."; - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + await DataSetUtils.deleteDataSet(connection, dataSetName); + result.success = true; + result.commandResponse = "Delete completed successfully."; + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } private async getContentsTag(dataSetName: string): Promise { diff --git a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts index 31077dbfef..14035a275b 100644 --- a/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts +++ b/packages/zowe-explorer-ftp-extension/src/ZoweExplorerFtpUssApi.ts @@ -24,26 +24,29 @@ import { AbstractFtpApi } from "./ZoweExplorerAbstractFtpApi"; // The Zowe FTP CLI plugin is written and uses mostly JavaScript, so relax the rules here. /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-explicit-any */ export class FtpUssApi extends AbstractFtpApi implements ZoweExplorerApi.IUss { public async fileList(ussFilePath: string): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - const response: any[] = await UssUtils.listFiles(connection, ussFilePath); - if (response) { - result.success = true; - result.apiResponse.items = response.map((element) => ({ - name: element.name, - size: element.size, - mtime: element.lastModified, - mode: element.permissions, - })); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + const response: any[] = await UssUtils.listFiles(connection, ussFilePath); + if (response) { + result.success = true; + result.apiResponse.items = response.map((element) => ({ + name: element.name, + size: element.size, + mtime: element.lastModified, + mode: element.permissions, + })); + } } + return result; + } finally { + this.releaseConnection(connection); } - return result; } // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await, require-await @@ -52,7 +55,6 @@ export class FtpUssApi extends AbstractFtpApi implements ZoweExplorerApi.IUss { } public async getContents(ussFilePath: string, options: zowe.IDownloadOptions): Promise { - const connection = await this.ftpClient(this.checkedProfile()); const result = this.getDefaultResponse(); const targetFile = options.file; const transferOptions = { @@ -60,16 +62,22 @@ export class FtpUssApi extends AbstractFtpApi implements ZoweExplorerApi.IUss { localFile: targetFile, size: 1, }; - if (connection && targetFile) { - imperative.IO.createDirsSyncFromFilePath(targetFile); - await UssUtils.downloadFile(connection, ussFilePath, transferOptions); - result.success = true; - result.commandResponse = ""; - result.apiResponse.etag = await this.hashFile(targetFile); - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection && targetFile) { + imperative.IO.createDirsSyncFromFilePath(targetFile); + await UssUtils.downloadFile(connection, ussFilePath, transferOptions); + result.success = true; + result.commandResponse = ""; + result.apiResponse.etag = await this.hashFile(targetFile); + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async putContents( @@ -85,37 +93,42 @@ export class FtpUssApi extends AbstractFtpApi implements ZoweExplorerApi.IUss { localFile: inputFilePath, }; const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (!connection) { - throw new Error(result.commandResponse); - } - // Save-Save with FTP requires loading the file first - if (returnEtag && etag) { - const tmpFileName = tmp.tmpNameSync(); - const options: zowe.IDownloadOptions = { - binary, - file: tmpFileName, - }; - const loadResult = await this.getContents(ussFilePath, options); - if ( - loadResult && - loadResult.success && - loadResult.apiResponse && - loadResult.apiResponse.etag && - loadResult.apiResponse.etag !== etag - ) { - // TODO: extension.ts should not check for zosmf errors. - throw new Error("Rest API failure with HTTP(S) status 412"); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (!connection) { + throw new Error(result.commandResponse); } - } - await UssUtils.uploadFile(connection, ussFilePath, transferOptions); - result.success = true; - if (returnEtag) { - result.apiResponse.etag = await this.hashFile(inputFilePath); - } - result.commandResponse = "File updated."; + // Save-Save with FTP requires loading the file first + if (returnEtag && etag) { + const tmpFileName = tmp.tmpNameSync(); + const options: zowe.IDownloadOptions = { + binary, + file: tmpFileName, + }; + const loadResult = await this.getContents(ussFilePath, options); + if ( + loadResult && + loadResult.success && + loadResult.apiResponse && + loadResult.apiResponse.etag && + loadResult.apiResponse.etag !== etag + ) { + // TODO: extension.ts should not check for zosmf errors. + throw new Error("Rest API failure with HTTP(S) status 412"); + } + } + await UssUtils.uploadFile(connection, ussFilePath, transferOptions); + result.success = true; + if (returnEtag) { + result.apiResponse.etag = await this.hashFile(inputFilePath); + } + result.commandResponse = "File updated."; - return result; + return result; + } finally { + this.releaseConnection(connection); + } } public async uploadDirectory( @@ -148,62 +161,82 @@ export class FtpUssApi extends AbstractFtpApi implements ZoweExplorerApi.IUss { mode?: string ): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection && connection.client) { - if (type === "directory") { - await UssUtils.makeDirectory(connection, ussPath); - } else if (type === "File" || type === "file") { - const content = Buffer.from(CoreUtils.addCarriageReturns("")); - const transferOptions = { - transferType: TRANSFER_TYPE_ASCII, - content: content, - }; - await UssUtils.uploadFile(connection, ussPath, transferOptions); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection && connection.client) { + if (type === "directory") { + await UssUtils.makeDirectory(connection, ussPath); + } else if (type === "File" || type === "file") { + const content = Buffer.from(CoreUtils.addCarriageReturns("")); + const transferOptions = { + transferType: TRANSFER_TYPE_ASCII, + content: content, + }; + await UssUtils.uploadFile(connection, ussPath, transferOptions); + } + result.success = true; + result.commandResponse = "Directory or file created."; + } else { + throw new Error(result.commandResponse); } - result.success = true; - result.commandResponse = "Directory or file created."; - } else { - throw new Error(result.commandResponse); + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async delete(ussPath: string, recursive?: boolean): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - if (recursive) { - await this.deleteDirectory(ussPath, connection); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + if (recursive) { + await this.deleteDirectory(ussPath, connection); + } else { + await UssUtils.deleteFile(connection, ussPath); + } + result.success = true; + result.commandResponse = "Delete completed."; } else { - await UssUtils.deleteFile(connection, ussPath); + throw new Error(result.commandResponse); } - result.success = true; - result.commandResponse = "Delete completed."; - } else { - throw new Error(result.commandResponse); + return result; + } finally { + this.releaseConnection(connection); } - return result; } public async rename(currentUssPath: string, newUssPath: string): Promise { const result = this.getDefaultResponse(); - const connection = await this.ftpClient(this.checkedProfile()); - if (connection) { - await UssUtils.renameFile(connection, currentUssPath, newUssPath); - result.success = true; - result.commandResponse = "Rename completed."; - } else { - throw new Error(result.commandResponse); + let connection: any; + try { + connection = await this.ftpClient(this.checkedProfile()); + if (connection) { + await UssUtils.renameFile(connection, currentUssPath, newUssPath); + result.success = true; + result.commandResponse = "Rename completed."; + } else { + throw new Error(result.commandResponse); + } + return result; + } finally { + this.releaseConnection(connection); } - return result; } private async deleteDirectory(ussPath: string, connection: any): Promise { const result = this.getDefaultResponse(); - const response: any = await UssUtils.deleteDirectory(connection, ussPath); - if (response) { - result.success = true; - result.commandResponse = "Delete Completed"; + try { + connection = await this.ftpClient(this.checkedProfile()); + const response: any = await UssUtils.deleteDirectory(connection, ussPath); + if (response) { + result.success = true; + result.commandResponse = "Delete Completed"; + } + } finally { + this.releaseConnection(connection); } }